Issues (1)

src/Auth/Source/Radius.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\radius\Auth\Source;
6
7
use Dapphp\Radius\Radius as RadiusClient;
8
use Exception;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\Error;
11
use SimpleSAML\Logger;
12
use SimpleSAML\Module\core\Auth\UserPassBase;
13
use SimpleSAML\Utils;
14
15
use function array_merge;
16
use function sprintf;
17
use function strtok;
18
use function var_export;
19
20
/**
21
 * RADIUS authentication source.
22
 *
23
 * This class is based on www/auth/login-radius.php.
24
 *
25
 * @package SimpleSAMLphp
26
 */
27
class Radius extends UserPassBase
28
{
29
    public const int RADIUS_USERNAME = 1;
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 29 at column 21
Loading history...
30
31
    public const int RADIUS_VENDOR_SPECIFIC = 26;
32
33
    public const int RADIUS_NAS_IDENTIFIER = 32;
34
35
36
    /**
37
     * @var array<mixed> The list of radius servers to use.
38
     */
39
    private array $servers;
40
41
    /**
42
     * @var string The hostname of the radius server.
43
     */
44
    private string $hostname;
45
46
    /**
47
     * @var int The port of the radius server.
48
     */
49
    private int $port;
50
51
    /**
52
     * @var string The secret used when communicating with the radius server.
53
     */
54
    private string $secret;
55
56
    /**
57
     * @var int The timeout for contacting the radius server.
58
     */
59
    private int $timeout;
60
61
    /**
62
     * @var string|null The realm to be added to the entered username.
63
     */
64
    private ?string $realm;
65
66
    /**
67
     * @var string|null The attribute name where the username should be stored.
68
     */
69
    private ?string $usernameAttribute = null;
70
71
    /**
72
     * @var int|null The vendor for the RADIUS attributes we are interrested in.
73
     */
74
    private ?int $vendor = null;
75
76
    /**
77
     * @var int The vendor-specific attribute for the RADIUS attributes we are
78
     *     interrested in.
79
     */
80
    private int $vendorType;
81
82
    /**
83
     * @var string|null The NAS-Identifier that should be set in Access-Request packets.
84
     */
85
    private ?string $nasIdentifier = null;
86
87
    /**
88
     * @var bool Debug modus
89
     */
90
    private bool $debug;
91
92
93
    /**
94
     * Constructor for this authentication source.
95
     *
96
     * @param array<mixed> $info  Information about this authentication source.
97
     * @param array<mixed> $config  Configuration.
98
     */
99
    public function __construct(array $info, array $config)
100
    {
101
        // Call the parent constructor first, as required by the interface
102
        parent::__construct($info, $config);
103
104
        // Parse configuration.
105
        $cfg = Configuration::loadFromArray(
106
            $config,
107
            'Authentication source ' . var_export($this->authId, true),
108
        );
109
110
        $this->servers = $cfg->getArray('servers');
111
        // For backwards compatibility
112
        if (empty($this->servers)) {
113
            $this->hostname = $cfg->getString('hostname');
114
            $this->port = $cfg->getOptionalIntegerRange('port', 1, 65535, 1812);
115
            $this->secret = $cfg->getString('secret');
116
            $this->servers[] = [
117
                'hostname' => $this->hostname,
118
                'port' => $this->port,
119
                'secret' => $this->secret,
120
            ];
121
        }
122
        $this->debug = $cfg->getOptionalBoolean('debug', false);
123
        $this->timeout = $cfg->getOptionalInteger('timeout', 5);
124
        $this->realm = $cfg->getOptionalString('realm', null);
125
        $this->usernameAttribute = $cfg->getOptionalString('username_attribute', null);
126
        $this->nasIdentifier = $cfg->getOptionalString('nas_identifier', null);
127
128
        $this->vendor = $cfg->getOptionalInteger('attribute_vendor', null);
129
        if ($this->vendor !== null) {
130
            $this->vendorType = $cfg->getInteger('attribute_vendor_type');
131
        }
132
    }
133
134
135
    /**
136
     * Attempt to log in using the given username and password.
137
     *
138
     * @param string $username  The username the user wrote.
139
     * @param string $password  The password the user wrote.
140
     * @return array<mixed> Associative array with the user's attributes.
141
     */
142
    protected function login(
143
        string $username,
144
        #[\SensitiveParameter]
145
        string $password,
146
    ): array {
147
        $radius = new RadiusClient();
148
        $response = false;
149
150
        // Try to add all radius servers, trigger a failure if no one works
151
        foreach ($this->servers as $server) {
152
            $radius->setServer($server['hostname']);
153
            $radius->setAuthenticationPort($server['port']);
154
            $radius->setSecret($server['secret']);
155
            $radius->setDebug($this->debug);
156
            $radius->setTimeout($this->timeout);
157
            $radius->setIncludeMessageAuthenticator();
158
159
            $httpUtils = new Utils\HTTP();
160
            $radius->setNasIpAddress($_SERVER['SERVER_ADDR'] ?: $httpUtils->getSelfHost());
161
162
            if ($this->nasIdentifier !== null) {
163
                $radius->setAttribute((string)self::RADIUS_NAS_IDENTIFIER, $this->nasIdentifier);
164
            }
165
166
            if ($this->realm !== null) {
167
                $radius->setRadiusSuffix('@' . $this->realm);
168
            }
169
            $response = $radius->accessRequest($username, $password);
170
171
            if ($response !== false) {
172
                break;
173
            }
174
        }
175
176
        if ($response === false) {
177
            $errorCode = $radius->getErrorCode();
178
            switch ($errorCode) {
179
                case $radius::TYPE_ACCESS_REJECT:
180
                    Logger::warning('ldapRadius: Radius authentication failed.');
181
                    throw new Error\Error('WRONGUSERPASS');
182
                case $radius::TYPE_ACCESS_CHALLENGE:
183
                    throw new Exception('Radius authentication error: Challenge requested, but not supported.');
184
                default:
185
                    throw new Exception(sprintf(
186
                        'Error during radius authentication; %s (%d)',
187
                        $radius->getErrorMessage(),
188
                        $errorCode,
189
                    ));
190
            }
191
        }
192
193
        Logger::info('ldapRadius: Radius authentication succeeded.');
194
195
        // If we get this far, we have a valid login
196
        $attributes = [];
197
        if ($this->usernameAttribute !== null) {
198
            $attributes[$this->usernameAttribute] = [$username];
199
        }
200
201
        if ($this->vendor === null) {
202
            /*
203
             * We aren't interested in any vendor-specific attributes. We are
204
             * therefore done now.
205
             */
206
            return $attributes;
207
        } else {
208
            foreach ($radius->getReceivedAttributes() as $content) {
209
                if ($content[0] == 26) { // is a Vendor-Specific attribute
210
                    $vsa = $radius->decodeVendorSpecificContent($content[1]);
211
212
                    // matches configured Vendor and Type
213
                    if ($vsa[0][0] === $this->vendor && $vsa[0][1] === $this->vendorType) {
214
                        // SAML attributes expected in a URN=value, so split at first =
215
                        $decomposed = explode("=", $vsa[0][2], 2);
216
                        $attributes[$decomposed[0]][] = $decomposed[1];
217
                    }
218
                }
219
            }
220
        }
221
222
        return array_merge($attributes, $this->getAttributes($radius));
223
    }
224
225
226
    /**
227
     * @param \Dapphp\Radius\Radius $radius
228
     * @return array<mixed>
229
     */
230
    private function getAttributes(RadiusClient $radius): array
231
    {
232
        // get AAI attribute sets.
233
        $resa = $radius->getReceivedAttributes();
234
        $attributes = [];
235
236
        // Use the received user name
237
        if ($resa['attr'] === self::RADIUS_USERNAME && $this->usernameAttribute !== null) {
238
            $attributes[$this->usernameAttribute] = [$resa['data']];
239
            return $attributes;
240
        }
241
242
        if ($resa['attr'] !== self::RADIUS_VENDOR_SPECIFIC) {
243
            return $attributes;
244
        }
245
246
        $resv = $resa['data'];
247
        if ($resv === false) {
248
            throw new Exception(sprintf(
249
                'Error getting vendor specific attribute: %s (%d)',
250
                $radius->getErrorMessage(),
251
                $radius->getErrorCode(),
252
            ));
253
        }
254
255
        $vendor = $resv['vendor'];
256
        $attrv = $resv['attr'];
257
        $datav = $resv['data'];
258
259
        if ($vendor !== $this->vendor || $attrv !== $this->vendorType) {
260
            return $attributes;
261
        }
262
263
        $attrib_name = strtok($datav, '=');
264
        $attrib_value = strtok('=');
265
266
        $attributes[$attrib_name] = [$attrib_value];
267
268
        return $attributes;
269
    }
270
}
271