InvalidCredentialResult::getCode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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