Completed
Push — master ( 1d0faf...7a5aed )
by Kacper
05:34
created

SaslAuthenticator::handleAuthResult()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
cc 2
eloc 6
nc 2
nop 2
ccs 0
cts 7
cp 0
crap 6
rs 9.4285
1
<?php
2
/**
3
 * XMPP Library
4
 *
5
 * Copyright (C) 2016, Some right reserved.
6
 *
7
 * @author Kacper "Kadet" Donat <[email protected]>
8
 *
9
 * Contact with author:
10
 * Xmpp: [email protected]
11
 * E-mail: [email protected]
12
 *
13
 * From Kadet with love.
14
 */
15
16
namespace Kadet\Xmpp\Module;
17
18
use Fabiang\Sasl\Authentication\AuthenticationInterface;
19
use Fabiang\Sasl\Authentication\ChallengeAuthenticationInterface;
20
use Fabiang\Sasl\Exception\InvalidArgumentException;
21
use Fabiang\Sasl\Sasl;
22
use Kadet\Xmpp\Exception\Protocol\AuthenticationException;
23
use Kadet\Xmpp\Stream\Features;
24
use Kadet\Xmpp\Utils\filter as with;
25
use Kadet\Xmpp\Xml\XmlElement;
26
use Kadet\Xmpp\XmppClient;
27
use Kadet\Xmpp\XmppClientModule;
28
29
class SaslAuthenticator extends XmppClientModule implements Authenticator
30
{
31
    const XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl';
32
33
    /**
34
     * Client's password used in authorisation.
35
     *
36
     * @var string
37
     */
38
    private $_password;
39
40
    /**
41
     * Authentication constructor.
42
     *
43
     * @param string $password Client's password
44
     */
45
    public function __construct($password)
46
    {
47
        $this->_password = $password;
48
    }
49
50
    public function setClient(XmppClient $client)
51
    {
52
        parent::setClient($client);
53
        $client->on('features', function (Features $features) {
54
            return $this->handleFeatures($features);
55
        });
56
    }
57
58
    protected function handleFeatures(Features $features)
59
    {
60
        if (!empty($features->mechanisms)) {
61
            $sasl = new Sasl();
62
            foreach ($features->mechanisms as $name) {
63
                try {
64
                    $mechanism = $sasl->factory($name, [
65
                        'authcid'  => $this->_client->jid->local,
66
                        'secret'   => $this->_password,
67
                        'hostname' => $this->_client->jid->domain,
68
                        'service'  => 'xmpp'
69
                    ]);
70
71
                    $this->_client->getLogger()->debug('Starting auth using {name} mechanism.', ['name' => $name]);
72
73
                    $auth = new XmlElement('auth', self::XMLNS);
74
                    $auth->setAttribute('mechanism', $name);
75
                    if ($mechanism instanceof ChallengeAuthenticationInterface) {
76
                        try {
77
                            $response = base64_encode($mechanism->createResponse());
78
                        } catch (InvalidArgumentException $e) {
79
                            $response = '=';
80
                        }
81
82
                        $auth->append($response);
83
84
                        $callback = $this->_client->on('element', function (XmlElement $challenge) use ($mechanism) {
85
                            $this->handleChallenge($challenge, $mechanism);
86
                        }, with\all(with\tag('challenge'), with\xmlns(self::XMLNS)));
87
88
                        $this->_client->on('element', function (XmlElement $result) use ($callback) {
89
                            $this->handleAuthResult($result, $callback);
90
                        }, with\all(with\any(with\tag('success'), with\tag('failure')), with\xmlns(self::XMLNS)));
91
                    } else {
92
                        $auth->append(base64_encode($mechanism->createResponse()));
93
                    }
94
                    $this->_client->write($auth);
95
96
                    return false;
97
                } catch (InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
98
                }
99
            }
100
        }
101
102
        return true;
103
    }
104
105
    private function handleChallenge(XmlElement $challenge, AuthenticationInterface $mechanism)
106
    {
107
        $response = new XmlElement('response', self::XMLNS);
108
        $response->append(base64_encode($mechanism->createResponse(base64_decode($challenge->innerXml))));
109
110
        $this->_client->write($response);
111
    }
112
113
    private function handleAuthResult(XmlElement $result, callable $callback)
114
    {
115
        $this->_client->removeListener('element', $callback);
116
117
        if ($result->localName === 'failure') {
118
            throw new AuthenticationException('Unable to auth.', [trim($result->innerXml)]);
119
        }
120
121
        $this->_client->getLogger()->info('Successfully authorized as {name}.', ['name' => (string)$this->_client->jid]);
122
        $this->_client->restart();
123
    }
124
}
125