Passed
Push — master ( 3cfb54...333493 )
by Tim
03:03
created

InvalidCredentialResult::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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