InvalidCredentialResult   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 12
eloc 57
c 0
b 0
f 0
dl 0
loc 207
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A isRestricted() 0 3 1
A __construct() 0 4 1
A replaceCodeMap() 0 3 1
A getRawMessage() 0 3 1
A isInvalidCredential() 0 3 1
A isAccountError() 0 3 1
A updateCodeMap() 0 3 1
A isPasswordError() 0 3 1
A fromDiagnosticMessage() 0 12 3
A getCode() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\ldap\Auth;
6
7
use function array_merge_recursive;
8
use function explode;
9
use function in_array;
10
use function preg_match;
11
use function str_replace;
12
use function strpos;
13
14
/**
15
 * Class representing an InvalidCredential Result
16
 *
17
 * This is used for extended diagnostic information
18
 *
19
 * @package simplesamlphp/simplesamlphp-module-ldap
20
 */
21
class InvalidCredentialResult
22
{
23
    /**
24
     * List of Active Directory Bind Error Short Description's
25
     *
26
     * @see https://ldapwiki.com/wiki/Common%20Active%20Directory%20Bind%20Errors
27
     */
28
    public const LDAP_NO_SUCH_OBJECT = '525';
29
30
    public const ERROR_LOGON_FAILURE = '52e';
31
32
    public const ERROR_ACCOUNT_RESTRICTION = '52f';
33
34
    public const ERROR_INVALID_LOGON_HOURS = '530';
35
36
    public const ERROR_INVALID_WORKSTATION = '531';
37
38
    public const ERROR_PASSWORD_EXPIRED = '532';
39
40
    public const ERROR_ACCOUNT_DISABLED = '533';
41
42
    public const ERROR_TOO_MANY_CONTEXT_IDS = '568';
43
44
    public const ERROR_ACCOUNT_EXPIRED = '701';
45
46
    public const ERROR_PASSWORD_MUST_CHANGE = '773';
47
48
    public const ERROR_ACCOUNT_LOCKED_OUT = '775';
49
50
    /**
51
     * List of Simple Bind error codes
52
     *
53
     * N.B. - This is an incomplete list
54
     */
55
    public const NT_STATUS_PASSWORD_EXPIRED = 'PASSWORD_EXPIRED';
56
57
    public const NT_STATUS_PASSWORD_MUST_CHANGE = 'PASSWORD_MUST_CHANGE';
58
59
    public const NT_STATUS_LOGON_FAILURE = 'LOGON_FAILURE';
60
61
    /**
62
     * List of keys for the code mapping
63
     */
64
    public const KEY_INVALID_CREDENTIAL = 'invalid_credential';
65
66
    public const KEY_PASSWORD_ERROR = 'password_error';
67
68
    public const KEY_ACCOUNT_ERROR = 'account_error';
69
70
    public const KEY_RESTRICTION = 'restriction';
71
72
73
    /**
74
     * Map of keys to check the code against when using is* methods
75
     *
76
     * @var array<string, array<string>>
77
     */
78
    protected array $codeMap = [
79
        self::KEY_INVALID_CREDENTIAL => [
80
            self::ERROR_LOGON_FAILURE,
81
            self::LDAP_NO_SUCH_OBJECT,
82
            self::NT_STATUS_LOGON_FAILURE,
83
        ],
84
        self::KEY_PASSWORD_ERROR => [
85
            self::ERROR_PASSWORD_EXPIRED,
86
            self::ERROR_PASSWORD_MUST_CHANGE,
87
            self::NT_STATUS_PASSWORD_EXPIRED,
88
            self::NT_STATUS_PASSWORD_MUST_CHANGE,
89
        ],
90
        self::KEY_ACCOUNT_ERROR => [
91
            self::ERROR_ACCOUNT_DISABLED,
92
            self::ERROR_ACCOUNT_EXPIRED,
93
            self::ERROR_ACCOUNT_LOCKED_OUT,
94
        ],
95
        self::KEY_RESTRICTION => [
96
            self::ERROR_ACCOUNT_RESTRICTION,
97
            self::ERROR_INVALID_LOGON_HOURS,
98
            self::ERROR_INVALID_WORKSTATION,
99
            self::ERROR_TOO_MANY_CONTEXT_IDS,
100
        ],
101
    ];
102
103
    /**
104
     * For Simple Binds this is the part after NT_STATUS_
105
     * Otherwise it is the HEX code from `data ([0-9a-f]+)`
106
     *
107
     * @var string|null The error code.
108
     */
109
    protected ?string $code;
110
111
    /**
112
     * @var string the message as it came from LDAP
113
     */
114
    protected string $rawMessage;
115
116
117
    /**
118
     * Parses the message when possible to determine what the actual error is
119
     *
120
     * @param string $message
121
     *
122
     * @return \SimpleSAML\Module\ldap\Auth\InvalidCredentialResult
123
     */
124
    public static function fromDiagnosticMessage(string $message): self
125
    {
126
        if (strpos($message, 'Simple Bind Failed:') === 0) {
127
            list(, $tmp) = explode(':', $message, 2);
128
            $code = str_replace('NT_STATUS_', '', $tmp);
129
        } elseif (preg_match('/data\s(.*)?,/', $message, $match)) {
130
            $code = $match[1];
131
        } else {
132
            $code = null;
133
        }
134
135
        return new self($code, $message);
136
    }
137
138
139
    /**
140
     * @param string|null $code
141
     * @param string $rawMessage
142
     */
143
    protected function __construct(?string $code, string $rawMessage)
144
    {
145
        $this->code = $code;
146
        $this->rawMessage = $rawMessage;
147
    }
148
149
150
    /**
151
     * Returns the code that was pulled from the raw message
152
     *
153
     * @return string|null
154
     */
155
    public function getCode(): ?string
156
    {
157
        return $this->code;
158
    }
159
160
161
    /**
162
     * Returns the raw message
163
     *
164
     * @return string
165
     */
166
    public function getRawMessage(): string
167
    {
168
        return $this->rawMessage;
169
    }
170
171
172
    /**
173
     * Allows the default code mappings to be updated
174
     * @param array<mixed> $codes
175
     * @return void
176
     */
177
    public function updateCodeMap(array $codes): void
178
    {
179
        $this->codeMap = array_merge_recursive($this->codeMap, $codes);
180
    }
181
182
183
    /**
184
     * Allows the default code mappings to be replaced
185
     *
186
     * @param array<mixed> $codes
187
     * @return void
188
     */
189
    public function replaceCodeMap(array $codes): void
190
    {
191
        $this->codeMap = $codes;
192
    }
193
194
195
    /**
196
     * @return bool Whether or not the password had an error
197
     */
198
    public function isPasswordError(): bool
199
    {
200
        return in_array($this->code, $this->codeMap[self::KEY_PASSWORD_ERROR]);
201
    }
202
203
204
    /**
205
     * @return bool Whether or not the account had an error
206
     */
207
    public function isAccountError(): bool
208
    {
209
        return in_array($this->code, $this->codeMap[self::KEY_ACCOUNT_ERROR]);
210
    }
211
212
213
    /**
214
     * @return bool Whether or not there was an auth problem
215
     */
216
    public function isInvalidCredential(): bool
217
    {
218
        return in_array($this->code, $this->codeMap[self::KEY_INVALID_CREDENTIAL]);
219
    }
220
221
222
    /**
223
     * @return bool Whether or not there is a restriction in place
224
     */
225
    public function isRestricted(): bool
226
    {
227
        return in_array($this->code, $this->codeMap[self::KEY_RESTRICTION]);
228
    }
229
}
230