Passed
Pull Request — master (#32)
by Tim
08:40
created

Ldap::resolveConnector()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 16
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 23
rs 9.7333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\ldap\Auth\Source;
6
7
use SimpleSAML\Assert\Assert;
8
use SimpleSAML\Configuration;
9
use SimpleSAML\Error;
10
use SimpleSAML\Module\core\Auth\UserPassBase;
11
use SimpleSAML\Module\ldap\Connector;
12
use SimpleSAML\Module\ldap\ConnectorInterface;
13
use Symfony\Component\Ldap\Adapter\ExtLdap\Query;
14
15
use function array_fill_keys;
16
use function array_keys;
17
use function array_map;
18
use function array_values;
19
use function sprintf;
20
use function str_replace;
21
use function var_export;
22
23
/**
24
 * LDAP authentication source.
25
 *
26
 * See the ldap-entry in config-templates/authsources.php for information about
27
 * configuration of this authentication source.
28
 *
29
 * @package simplesamlphp/simplesamlphp-module-ldap
30
 */
31
32
class Ldap extends UserPassBase
33
{
34
    /**
35
     * @var \SimpleSAML\Module\ldap\ConnectorInterface
36
     */
37
    protected ConnectorInterface $connector;
38
39
    /**
40
     * An LDAP configuration object.
41
     */
42
    protected Configuration $ldapConfig;
43
44
45
    /**
46
     * Constructor for this authentication source.
47
     *
48
     * @param array $info  Information about this authentication source.
49
     * @param array $config  Configuration.
50
     */
51
    public function __construct(array $info, array $config)
52
    {
53
        // Call the parent constructor first, as required by the interface
54
        parent::__construct($info, $config);
55
56
        $this->ldapConfig = Configuration::loadFromArray(
57
            $config,
58
            'authsources[' . var_export($this->authId, true) . ']'
59
        );
60
61
        $this->connector = $this->resolveConnector();
62
    }
63
64
65
    /**
66
     * Attempt to log in using the given username and password.
67
     *
68
     * @param string $username  The username the user wrote.
69
     * @param string $password  The password the user wrote.
70
     * @return array  Associative array with the users attributes.
71
     */
72
    protected function login(string $username, string $password): array
73
    {
74
        $encryption = $this->ldapConfig->getOptionalString('encryption', 'ssl');
75
        Assert::oneOf($encryption, ['none', 'ssl', 'tls']);
76
77
        $version = $this->ldapConfig->getOptionalInteger('version', 3);
78
        Assert::positiveInteger($version);
79
80
        $timeout = $this->ldapConfig->getOptionalInteger('timeout', 3);
81
        Assert::positiveInteger($timeout);
82
83
        $ldapUtils = new Utils\Ldap();
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\ldap\Auth\Source\Utils\Ldap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
84
        $ldapObject = $ldapUtils->create(
0 ignored issues
show
Unused Code introduced by
The assignment to $ldapObject is dead and can be removed.
Loading history...
85
            $this->ldapConfig->getString('connection_string'),
86
            $encryption,
87
            $version,
88
            $this->ldapConfig->getOptionalString('extension', 'ext_ldap'),
89
            $this->ldapConfig->getOptionalBoolean('debug', false),
90
            $this->ldapConfig->getOptionalArray('options', []),
91
        );
92
93
        $searchScope = $this->ldapConfig->getOptionalString('search.scope', Query::SCOPE_SUB);
94
        Assert::oneOf($searchScope, [Query::SCOPE_BASE, Query::SCOPE_ONE, Query::SCOPE_SUB]);
95
96
        $timeout = $this->ldapConfig->getOptionalInteger('timeout', 3);
97
        $searchBase = $this->ldapConfig->getArray('search.base');
98
        $options = [
99
            'scope' => $searchScope,
100
            'timeout' => $timeout,
101
        ];
102
103
        $searchEnable = $this->ldapConfig->getOptionalBoolean('search.enable', false);
104
        if ($searchEnable === false) {
105
            $dnPattern = $this->ldapConfig->getString('dnpattern');
106
            $dn = str_replace('%username%', $username, $dnPattern);
107
        } else {
108
            $searchUsername = $this->ldapConfig->getString('search.username');
109
            Assert::notWhitespaceOnly($searchUsername);
110
111
            $searchPassword = $this->ldapConfig->getOptionalString('search.password', null);
112
            Assert::nullOrnotWhitespaceOnly($searchPassword);
113
114
            $searchAttributes = $this->ldapConfig->getArray('search.attributes');
115
            $searchFilter = $this->ldapConfig->getOptionalString('search.filter', null);
116
117
            try {
118
                $this->connector->bind($searchUsername, $searchPassword);
119
            } catch (Error\Error $e) {
120
                throw new Error\Exception("Unable to bind using the configured search.username and search.password.");
121
            }
122
123
            $filter = '';
124
            foreach ($searchAttributes as $attr) {
125
                $filter .= '(' . $attr . '=' . $username . ')';
126
            }
127
            $filter = '(|' . $filter . ')';
128
129
            // Append LDAP filters if defined
130
            if ($searchFilter !== null) {
131
                $filter = "(&" . $filter . $searchFilter . ")";
132
            }
133
134
            try {
135
                /** @psalm-var \Symfony\Component\Ldap\Entry $entry */
136
                $entry = $this->connector->search($searchBase, $filter, $options, false);
137
                $dn = $entry->getDn();
138
            } catch (Error\Exception $e) {
139
                throw new Error\Error('WRONGUSERPASS');
140
            }
141
        }
142
143
        $this->connector->bind($dn, $password);
144
        $filter = sprintf('(distinguishedName=%s)', $dn);
145
146
        /** @psalm-var \Symfony\Component\Ldap\Entry $entry */
147
        $entry = $this->connector->search($searchBase, $filter, $options, false);
148
149
        $attributes = $this->ldapConfig->getOptionalValue('attributes', []);
150
        if ($attributes === null) {
151
            $result = $entry->getAttributes();
152
        } else {
153
            Assert::isArray($attributes);
154
            $result = array_intersect_key(
155
                $entry->getAttributes(),
156
                array_fill_keys(array_values($attributes), null)
157
            );
158
        }
159
160
        $binaries = array_intersect(
161
            array_keys($result),
162
            $this->ldapConfig->getOptionalArray('attributes.binary', []),
163
        );
164
        foreach ($binaries as $binary) {
165
            $result[$binary] = array_map('base64_encode', $result[$binary]);
166
        }
167
168
        return $result;
169
    }
170
171
    /**
172
     * Resolve the connector
173
     *
174
     * @return \SimpleSAML\Module\ldap\ConnectorInterface
175
     * @throws \Exception
176
     */
177
    protected function resolveConnector(): ConnectorInterface
178
    {
179
        if (!empty($this->connector)) {
180
            return $this->connector;
181
        }
182
183
        $encryption = $this->ldapConfig->getString('encryption', 'ssl');
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Configuration::getString() has too many arguments starting with 'ssl'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

183
        /** @scrutinizer ignore-call */ 
184
        $encryption = $this->ldapConfig->getString('encryption', 'ssl');

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
184
        Assert::oneOf($encryption, ['none', 'ssl', 'tls']);
185
186
        $version = $this->ldapConfig->getInteger('version', 3);
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Configuration::getInteger() has too many arguments starting with 3. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
        /** @scrutinizer ignore-call */ 
187
        $version = $this->ldapConfig->getInteger('version', 3);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
187
        Assert::positiveInteger($version);
188
189
        $class = $this->ldapConfig->getString('connector', Connector\Ldap::class);
190
        Assert::classExists($class);
191
        Assert::implementsInterface($class, ConnectorInterface::class);
192
193
        return $this->connector = new $class(
194
            $this->ldapConfig->getString('connection_string'),
195
            $encryption,
196
            $version,
197
            $this->ldapConfig->getString('extension', 'ext_ldap'),
198
            $this->ldapConfig->getBoolean('debug', false),
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Configuration::getBoolean() has too many arguments starting with false. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

198
            $this->ldapConfig->/** @scrutinizer ignore-call */ 
199
                               getBoolean('debug', false),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
199
            $this->ldapConfig->getArray('options', []),
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Configuration::getArray() has too many arguments starting with array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

199
            $this->ldapConfig->/** @scrutinizer ignore-call */ 
200
                               getArray('options', []),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
200
        );
201
    }
202
}
203