Completed
Push — master ( 7a5aed...4799e1 )
by Kacper
06:25
created

SaslAuthenticator::auth()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 47
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 6
eloc 32
nc 5
nop 1
dl 0
loc 47
ccs 0
cts 25
cp 0
crap 42
rs 8.5125
c 1
b 0
f 1
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
28
class SaslAuthenticator extends ClientModule implements Authenticator
29
{
30
    const XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl';
31
32
    /**
33
     * Client's password used in authorisation.
34
     *
35
     * @var string
36
     */
37
    private $_password;
38
39
    /**
40
     * Authentication constructor.
41
     *
42
     * @param string $password Client's password
43
     */
44
    public function __construct($password)
45
    {
46
        $this->_password = $password;
47
    }
48
49
    public function setClient(XmppClient $client)
50
    {
51
        parent::setClient($client);
52
53
        $client->on('features', function (Features $features) {
54
            return $this->auth($features);
55
        });
56
    }
57
58
    public function auth(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->once('element', function (XmlElement $result) use ($callback) {
89
                            $this->_client->removeListener('element', $callback);
90
                            $this->handleAuthResult($result, $callback);
91
                        }, with\all(with\any(with\tag('success'), with\tag('failure')), with\xmlns(self::XMLNS)));
92
                    } else {
93
                        $auth->append(base64_encode($mechanism->createResponse()));
94
                    }
95
                    $this->_client->write($auth);
96
97
                    return false;
98
                } catch (InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
99
                }
100
            }
101
        }
102
103
        return true;
104
    }
105
106
    private function handleChallenge(XmlElement $challenge, AuthenticationInterface $mechanism)
107
    {
108
        $response = new XmlElement('response', self::XMLNS);
109
        $response->append(base64_encode($mechanism->createResponse(base64_decode($challenge->innerXml))));
110
111
        $this->_client->write($response);
112
    }
113
114
    private function handleAuthResult(XmlElement $result, callable $callback)
0 ignored issues
show
Unused Code introduced by
The parameter $callback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
115
    {
116
        if ($result->localName === 'failure') {
117
            throw new AuthenticationException('Unable to auth. '.trim($result->innerXml));
118
        }
119
120
        $this->_client->getLogger()->info('Successfully authorized as {name}.', ['name' => (string)$this->_client->jid]);
121
        $this->_client->restart();
122
    }
123
124
    public function setPassword(string $password)
125
    {
126
        $this->_password = $password;
127
    }
128
}
129