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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
########################################################################
# File name: common.py
# This file is part of: aiosasl
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
import enum
import typing
class SASLError(Exception):
"""
Base class for a SASL related error. `opaque_error` may be anything but
:data:`None` which helps your application re-identify the error at the
outer layers. `kind` is a string which helps identifying the class of the
error; this is set implicitly by the constructors of :class:`SASLFailure`
and :class:`AuthenticationFailure`, which you are encouraged to use.
`text` may be a human-readable string describing the error condition in
more detail.
`opaque_error` is set to :data:`None` by :class:`SASLMechanism`
implementations to indicate errors which originate from the local mechanism
implementation.
.. attribute:: opaque_error
The value passed to the respective constructor argument.
.. attribute:: text
The value passed to the respective constructor argument.
"""
def __init__(
self,
opaque_error: typing.Any,
kind: str,
text: typing.Optional[str] = None):
msg = "{}: {}".format(opaque_error, kind)
if text:
msg += ": {}".format(text)
super().__init__(msg)
self.opaque_error = opaque_error
self.text = text
class AuthenticationFailure(SASLError):
"""
A SASL error which indicates that the provided credentials are
invalid. This may be raised by :class:`SASLInterface` methods.
"""
def __init__(
self,
opaque_error: typing.Any,
text: typing.Optional[str] = None):
super().__init__(opaque_error, "authentication failed", text=text)
class SASLFailure(SASLError):
"""
A SASL protocol failure which is unrelated to the credentials passed. This
may be raised by :class:`SASLInterface` methods.
"""
def __init__(
self,
opaque_error: typing.Any,
text: typing.Optional[str] = None):
super().__init__(opaque_error, "SASL failure", text=text)
def promote_to_authentication_failure(self) -> AuthenticationFailure:
return AuthenticationFailure(
self.opaque_error,
self.text)
class SASLState(enum.Enum):
"""
The states of the SASL state machine.
.. attribute:: CHALLENGE
the server sent a SASL challenge
.. attribute:: SUCCESS
the authentication was successful
.. attribute:: FAILURE
the authentication failed
Internal states used by the state machine:
.. attribute:: INITIAL
the state of the state machine before the
authentication is started
.. attribute:: SUCCESS_SIMULATE_CHALLENGE
used to unwrap success replies that carry final data
These internal states *must not* be returned by the
:class:`SASLInterface` methods as first component of the result
tuple.
The following method is used to process replies returned
by the :class:`SASLInterface` methods:
.. method:: from_reply
"""
INITIAL = "initial"
CHALLENGE = "challenge"
SUCCESS = "success"
FAILURE = "failure"
SUCCESS_SIMULATE_CHALLENGE = "success-simulate-challenge"
@classmethod
def from_reply(cls, state: "SASLState") -> "SASLState":
"""
Comptaibility layer for old :class:`SASLInterface`
implementations.
Accepts the follwing set of :class:`SASLState` or strings and
maps the strings to :class:`SASLState` elements as follows:
``"challenge"``
:member:`SASLState.CHALLENGE`
``"failue"``
:member:`SASLState.FAILURE`
``"success"``
:member:`SASLState.SUCCESS`
"""
if state in (SASLState.FAILURE, SASLState.SUCCESS,
SASLState.CHALLENGE):
return state
if state in ("failure", "success", "challenge"):
return SASLState(state)
else:
raise RuntimeError("invalid SASL state", state)
NextStateTuple = typing.Tuple[SASLState, typing.Optional[bytes]]
CredentialProvider = typing.Callable[
[], typing.Coroutine[typing.Any, typing.Any, typing.Tuple[str, str]]
]
|