summaryrefslogtreecommitdiff
path: root/sleekxmpp/features/feature_mechanisms.py
blob: 994c9bed758a0dfc4e510db10aa1a5f09d693dcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
    SleekXMPP: The Sleek XMPP Library
    Copyright (C) 2010  Nathanael C. Fritz
    This file is part of SleekXMPP.

    See the file LICENSE for copying permission.
"""

import logging

from sleekxmpp.stanza import stream
from sleekxmpp.xmlstream import RestartStream
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.plugins.base import base_plugin


log = logging.getLogger(__name__)


class feature_mechanisms(base_plugin):

    def plugin_init(self):
        self.name = 'SASL Mechanisms'
        self.rfc = '6120'
        self.description = "SASL Stream Feature"

        self.xmpp.register_stanza(stream.sasl.Success)
        self.xmpp.register_stanza(stream.sasl.Failure)
        self.xmpp.register_stanza(stream.sasl.Auth)

        self._mechanism_handlers = {}
        self._mechanism_priorities = []

        self.xmpp.register_handler(
                Callback('SASL Success',
                         MatchXPath(stream.sasl.Success.tag_name()),
                         self._handle_success,
                         instream=True,
                         once=True))
        self.xmpp.register_handler(
                Callback('SASL Failure',
                         MatchXPath(stream.sasl.Failure.tag_name()),
                         self._handle_fail,
                         instream=True,
                         once=True))

        self.xmpp.register_feature('mechanisms',
                self._handle_sasl_auth,
                restart=True,
                order=self.config.get('order', 100))

    def register_mechanism(self, name, handler, priority=0):
        """
        Register a handler for a SASL authentication mechanism.

        Arguments:
            name     -- The name of the mechanism (all caps)
            handler  -- The function that will perform the
                        authentication. The function must
                        return True if it is able to carry
                        out the authentication, False if
                        a required condition is not met.
            priority -- An integer value indicating the
                        preferred ordering for the mechanism.
                        High values will be attempted first.
        """
        self._mechanism_handlers[name] = handler
        self._mechanism_priorities.append((priority, name))
        self._mechanism_priorities.sort(reverse=True)

    def remove_mechanism(self, name):
        """
        Remove support for a given SASL authentication mechanism.

        Arguments:
            name -- The name of the mechanism to remove (all caps)
        """
        if name in self._mechanism_handlers:
            del self._mechanism_handlers[name]

        p = self._mechanism_priorities
        self._mechanism_priorities = [i for i in p if i[1] != name]

    def _handle_sasl_auth(self, features):
        """
        Handle authenticating using SASL.

        Arguments:
            features -- The stream features stanza.
        """
        for priority, mech in self._mechanism_priorities:
            if mech in features['mechanisms']:
                log.debug('Attempt to use SASL %s' % mech)
                if self._mechanism_handlers[mech]():
                    break
        else:
            log.error("No appropriate login method.")
            self.xmpp.event("no_auth", direct=True)
            self.xmpp.disconnect()

        return True

    def _handle_success(self, stanza):
        """SASL authentication succeeded. Restart the stream."""
        self.xmpp.authenticated = True
        self.xmpp.features.append('mechanisms')
        raise RestartStream()

    def _handle_fail(self, stanza):
        """SASL authentication failed. Disconnect and shutdown."""
        log.info("Authentication failed.")
        self.xmpp.event("failed_auth", direct=True)
        self.xmpp.disconnect()
        log.debug("Starting SASL Auth")
        return True