Skip to content

Commit 27dfbb4

Browse files
wip
1 parent 05168e4 commit 27dfbb4

File tree

4 files changed

+89
-20
lines changed

4 files changed

+89
-20
lines changed

tarantool/connection.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,8 @@ def __init__(self, host, port,
570570
ssl_ca_file=DEFAULT_SSL_CA_FILE,
571571
ssl_ciphers=DEFAULT_SSL_CIPHERS,
572572
packer_factory=default_packer_factory,
573-
unpacker_factory=default_unpacker_factory):
573+
unpacker_factory=default_unpacker_factory,
574+
auth_type=DEFAULT_AUTH_TYPE):
574575
"""
575576
:param host: Server hostname or IP address. Use ``None`` for
576577
Unix sockets.
@@ -710,6 +711,14 @@ def __init__(self, host, port,
710711
callable[[:obj:`~tarantool.Connection`], :obj:`~msgpack.Unpacker`],
711712
optional
712713
714+
:param auth_type: Authentication method: ``"chap-sha1"`` (supported in
715+
Tarantool CE and EE) or ``"pap-sha256"`` (supported in Tarantool EE,
716+
``"ssl"`` :paramref:`~tarantool.Connection.transport` must be used).
717+
If `None`, use authentication method provided by server in IPROTO_ID
718+
exchange. If server does not provide an authentication method, use
719+
``"chap-sha1"``.
720+
:type auth_type: :obj:`None` or :obj:`str`, optional
721+
713722
:raise: :exc:`~tarantool.error.ConfigurationError`,
714723
:meth:`~tarantool.Connection.connect` exceptions
715724
@@ -763,6 +772,8 @@ def __init__(self, host, port,
763772
}
764773
self._packer_factory_impl = packer_factory
765774
self._unpacker_factory_impl = unpacker_factory
775+
self._client_auth_type = auth_type
776+
self._server_auth_type = DEFAULT_AUTH_TYPE
766777

767778
if connect_now:
768779
self.connect()
@@ -1335,8 +1346,11 @@ def authenticate(self, user, password):
13351346
if not self._socket:
13361347
return self._opt_reconnect()
13371348

1338-
request = RequestAuthenticate(self, self._salt, self.user,
1339-
self.password)
1349+
request = RequestAuthenticate(self,
1350+
salt=self._salt,
1351+
user=self.user,
1352+
password=self.password,
1353+
type=self._get_auth_type())
13401354
auth_response = self._send_request_wo_reconnect(request)
13411355
if auth_response.return_code == 0:
13421356
self.flush_schema()
@@ -1982,11 +1996,13 @@ def _check_features(self):
19821996
response = self._send_request(request)
19831997
server_protocol_version = response.protocol_version
19841998
server_features = response.features
1999+
server_auth_type = response.auth_type
19852000
except DatabaseError as exc:
19862001
ER_UNKNOWN_REQUEST_TYPE = 48
19872002
if exc.code == ER_UNKNOWN_REQUEST_TYPE:
19882003
server_protocol_version = None
19892004
server_features = []
2005+
server_auth_type = None
19902006
else:
19912007
raise exc
19922008

@@ -1999,6 +2015,8 @@ def _check_features(self):
19992015
for val in features_list:
20002016
self._features[val] = True
20012017

2018+
self._server_auth_type = server_auth_type
2019+
20022020
def _packer_factory(self):
20032021
return self._packer_factory_impl(self)
20042022

@@ -2775,3 +2793,28 @@ def crud_unflatten_rows(self, rows: list, metadata: list) -> list:
27752793
res.append(row_res)
27762794

27772795
return res
2796+
2797+
def _get_auth_type(self):
2798+
"""
2799+
Get authentication method based on client and server settings.
2800+
2801+
:rtype: :obj:`str`
2802+
2803+
:raise: :exc:`~tarantool.error.DatabaseError`
2804+
"""
2805+
if self._client_auth_type == AUTH_TYPE_DEFAULT:
2806+
if self._server_auth_type == AUTH_TYPE_DEFAULT:
2807+
auth_type = AUTH_TYPE_CHAP_SHA1
2808+
else:
2809+
if self._server_auth_type not in AUTH_TYPES:
2810+
raise ConfigurationError(f'Unknown server authentication type {self._server_auth_type}')
2811+
auth_type = self._server_auth_type
2812+
else:
2813+
if self._client_auth_type not in AUTH_TYPES:
2814+
raise ConfigurationError(f'Unknown client authentication type {self._server_auth_type}')
2815+
auth_type = self._client_auth_type
2816+
2817+
if auth_type == AUTH_TYPE_PAP_SHA256 and self.transport != SSL_TRANSPORT:
2818+
raise ConfigurationError('Use PAP-SHA256 only with ssl transport')
2819+
2820+
return auth_type

tarantool/const.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#
4141
IPROTO_VERSION = 0x54
4242
IPROTO_FEATURES = 0x55
43+
IPROTO_AUTH_TYPE = 0x5b
4344
IPROTO_CHUNK = 0x80
4445

4546
IPROTO_GREETING_SIZE = 128
@@ -127,6 +128,14 @@
127128
# Default delay between attempts to reconnect (seconds)
128129
POOL_INSTANCE_RECONNECT_DELAY = 0
129130

131+
# Default authentication method
132+
DEFAULT_AUTH_TYPE = None
133+
# Authenticate with CHAP-SHA1 (Tarantool CE and EE)
134+
AUTH_TYPE_CHAP_SHA1 = "chap-sha1"
135+
# Authenticate with PAP-SHA256 (Tarantool EE)
136+
AUTH_TYPE_PAP_SHA256 = "pap-sha256"
137+
AUTH_TYPES = [AUTH_TYPE_CHAP_SHA1, AUTH_TYPE_PAP_SHA256]
138+
130139
# Tarantool master 948e5cdc (possible 2.11) protocol version is 4
131140
CONNECTOR_IPROTO_VERSION = 4
132141
# List of connector-supported features

tarantool/request.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,23 @@ def __init__(self, conn, space_no, values):
224224
self._body = request_body
225225

226226

227+
def _hash(sha, values):
228+
for i in values:
229+
if i is not None:
230+
if isinstance(i, bytes):
231+
sha.update(i)
232+
else:
233+
sha.update(i.encode())
234+
return sha.digest()
235+
227236
class RequestAuthenticate(Request):
228237
"""
229238
Represents AUTHENTICATE request.
230239
"""
231240

232241
request_type = REQUEST_TYPE_AUTHENTICATE
233242

234-
def __init__(self, conn, salt, user, password):
243+
def __init__(self, conn, salt, user, password, auth_type):
235244
"""
236245
:param conn: Request sender.
237246
:type conn: :class:`~tarantool.Connection`
@@ -250,23 +259,19 @@ def __init__(self, conn, salt, user, password):
250259

251260
super(RequestAuthenticate, self).__init__(conn)
252261

253-
def sha1(values):
262+
if auth_type == AUTH_TYPE_CHAP_SHA1:
254263
sha = hashlib.sha1()
255-
for i in values:
256-
if i is not None:
257-
if isinstance(i, bytes):
258-
sha.update(i)
259-
else:
260-
sha.update(i.encode())
261-
return sha.digest()
262-
263-
hash1 = sha1((password,))
264-
hash2 = sha1((hash1,))
265-
scramble = sha1((salt, hash2))
266-
scramble = strxor(hash1, scramble)
267-
request_body = self._dumps({IPROTO_USER_NAME: user,
268-
IPROTO_TUPLE: ("chap-sha1", scramble)})
269-
self._body = request_body
264+
hash1 = _hash(sha, password,)
265+
hash2 = _hash(sha, (hash1,))
266+
prescramble = _hash(sha, (salt, hash2))
267+
scramble = strxor(hash1, prescramble)
268+
elif auth_type == AUTH_TYPE_CHAP_SHA1:
269+
scramble = password
270+
else:
271+
raise ValueError(f'Unexpected auth_type {auth_type}')
272+
273+
self._body = self._dumps({IPROTO_USER_NAME: user,
274+
IPROTO_TUPLE: (auth_type, scramble)})
270275

271276
def header(self, length):
272277
"""

tarantool/response.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,15 @@ def features(self):
386386
return []
387387
return self._body.get(IPROTO_FEATURES)
388388

389+
@property
390+
def auth_type(self):
391+
"""
392+
Server expected authentication method.
393+
394+
:rtype: :obj:`str` or :obj:`None`
395+
"""
396+
397+
if self._return_code != 0:
398+
return DEFAULT_AUTH_TYPE
399+
return self._body.get(IPROTO_AUTH_TYPE)
400+

0 commit comments

Comments
 (0)