Completed
Push — master ( 634839...cd1596 )
by Kacper
03:22
created

SaslAuthenticator::tryMechanism()   B

Complexity

Conditions 4
Paths 18

Size

Total Lines 40
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 29
c 1
b 0
f 0
nc 18
nop 2
dl 0
loc 40
ccs 0
cts 22
cp 0
crap 20
rs 8.5806
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
                if($this->tryMechanism($sasl, $name)) {
64
                    return false;
65
                }
66
            }
67
        }
68
69
        return true;
70
    }
71
72
    private function tryMechanism(Sasl $sasl, $name) {
73
        try {
74
            $mechanism = $sasl->factory($name, [
75
                'authcid'  => $this->_client->jid->local,
76
                'secret'   => $this->_password,
77
                'hostname' => $this->_client->jid->domain,
78
                'service'  => 'xmpp'
79
            ]);
80
81
            $this->_client->getLogger()->debug('Starting auth using {name} mechanism.', ['name' => $name]);
82
83
            $auth = new XmlElement('auth', self::XMLNS);
84
            $auth->setAttribute('mechanism', $name);
85
            if ($mechanism instanceof ChallengeAuthenticationInterface) {
86
                try {
87
                    $response = base64_encode($mechanism->createResponse());
88
                } catch (InvalidArgumentException $e) {
89
                    $response = '=';
90
                }
91
92
                $auth->append($response);
93
94
                $callback = $this->_client->on('element', function (XmlElement $challenge) use ($mechanism) {
95
                    $this->handleChallenge($challenge, $mechanism);
96
                }, with\all(with\tag('challenge'), with\xmlns(self::XMLNS)));
97
98
                $this->_client->once('element', function (XmlElement $result) use ($callback) {
99
                    $this->_client->removeListener('element', $callback);
100
                    $this->handleAuthResult($result, $callback);
101
                }, with\all(with\any(with\tag('success'), with\tag('failure')), with\xmlns(self::XMLNS)));
102
            } else {
103
                $auth->append(base64_encode($mechanism->createResponse()));
104
            }
105
            $this->_client->write($auth);
106
107
            return true;
108
        } catch (InvalidArgumentException $e) {
109
            return false;
110
        }
111
    }
112
113
    private function handleChallenge(XmlElement $challenge, AuthenticationInterface $mechanism)
114
    {
115
        $response = new XmlElement('response', self::XMLNS);
116
        $response->append(base64_encode($mechanism->createResponse(base64_decode($challenge->innerXml))));
117
118
        $this->_client->write($response);
119
    }
120
121
    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...
122
    {
123
        if ($result->localName === 'failure') {
124
            throw new AuthenticationException('Unable to auth. '.trim($result->innerXml));
125
        }
126
127
        $this->_client->getLogger()->info('Successfully authorized as {name}.', ['name' => (string)$this->_client->jid]);
128
        $this->_client->restart();
129
    }
130
131
    public function setPassword(string $password)
132
    {
133
        $this->_password = $password;
134
    }
135
}
136