mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
9.1 KiB
Python
273 lines
9.1 KiB
Python
# Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License, version 2.0, as
|
|
# published by the Free Software Foundation.
|
|
#
|
|
# This program is also distributed with certain software (including
|
|
# but not limited to OpenSSL) that is licensed under separate terms,
|
|
# as designated in a particular file or component or in included license
|
|
# documentation. The authors of MySQL hereby grant you an
|
|
# additional permission to link the program and your derivative works
|
|
# with the separately licensed software that they have included with
|
|
# MySQL.
|
|
#
|
|
# Without limiting anything contained in the foregoing, this file,
|
|
# which is part of MySQL Connector/Python, is also subject to the
|
|
# Universal FOSS Exception, version 1.0, a copy of which can be found at
|
|
# http://oss.oracle.com/licenses/universal-foss-exception.
|
|
#
|
|
# 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 General Public License, version 2.0, for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
"""Implementing support for MySQL Authentication Plugins"""
|
|
|
|
from hashlib import sha1, sha256
|
|
import struct
|
|
|
|
from . import errors
|
|
from .catch23 import PY2, isstr, UNICODE_TYPES
|
|
|
|
|
|
class BaseAuthPlugin(object):
|
|
"""Base class for authentication plugins
|
|
|
|
|
|
Classes inheriting from BaseAuthPlugin should implement the method
|
|
prepare_password(). When instantiating, auth_data argument is
|
|
required. The username, password and database are optional. The
|
|
ssl_enabled argument can be used to tell the plugin whether SSL is
|
|
active or not.
|
|
|
|
The method auth_response() method is used to retrieve the password
|
|
which was prepared by prepare_password().
|
|
"""
|
|
|
|
requires_ssl = False
|
|
plugin_name = ''
|
|
|
|
def __init__(self, auth_data, username=None, password=None, database=None,
|
|
ssl_enabled=False):
|
|
"""Initialization"""
|
|
self._auth_data = auth_data
|
|
self._username = username
|
|
self._password = password
|
|
self._database = database
|
|
self._ssl_enabled = ssl_enabled
|
|
|
|
def prepare_password(self):
|
|
"""Prepares and returns password to be send to MySQL
|
|
|
|
This method needs to be implemented by classes inheriting from
|
|
this class. It is used by the auth_response() method.
|
|
|
|
Raises NotImplementedError.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def auth_response(self):
|
|
"""Returns the prepared password to send to MySQL
|
|
|
|
Raises InterfaceError on errors. For example, when SSL is required
|
|
by not enabled.
|
|
|
|
Returns str
|
|
"""
|
|
if self.requires_ssl and not self._ssl_enabled:
|
|
raise errors.InterfaceError("{name} requires SSL".format(
|
|
name=self.plugin_name))
|
|
return self.prepare_password()
|
|
|
|
|
|
class MySQLNativePasswordAuthPlugin(BaseAuthPlugin):
|
|
"""Class implementing the MySQL Native Password authentication plugin"""
|
|
|
|
requires_ssl = False
|
|
plugin_name = 'mysql_native_password'
|
|
|
|
def prepare_password(self):
|
|
"""Prepares and returns password as native MySQL 4.1+ password"""
|
|
if not self._auth_data:
|
|
raise errors.InterfaceError("Missing authentication data (seed)")
|
|
|
|
if not self._password:
|
|
return b''
|
|
password = self._password
|
|
|
|
if isstr(self._password):
|
|
password = self._password.encode('utf-8')
|
|
else:
|
|
password = self._password
|
|
|
|
if PY2:
|
|
password = buffer(password) # pylint: disable=E0602
|
|
try:
|
|
auth_data = buffer(self._auth_data) # pylint: disable=E0602
|
|
except TypeError:
|
|
raise errors.InterfaceError("Authentication data incorrect")
|
|
else:
|
|
password = password
|
|
auth_data = self._auth_data
|
|
|
|
hash4 = None
|
|
try:
|
|
hash1 = sha1(password).digest()
|
|
hash2 = sha1(hash1).digest()
|
|
hash3 = sha1(auth_data + hash2).digest()
|
|
if PY2:
|
|
xored = [ord(h1) ^ ord(h3) for (h1, h3) in zip(hash1, hash3)]
|
|
else:
|
|
xored = [h1 ^ h3 for (h1, h3) in zip(hash1, hash3)]
|
|
hash4 = struct.pack('20B', *xored)
|
|
except Exception as exc:
|
|
raise errors.InterfaceError(
|
|
"Failed scrambling password; {0}".format(exc))
|
|
|
|
return hash4
|
|
|
|
|
|
class MySQLClearPasswordAuthPlugin(BaseAuthPlugin):
|
|
"""Class implementing the MySQL Clear Password authentication plugin"""
|
|
|
|
requires_ssl = True
|
|
plugin_name = 'mysql_clear_password'
|
|
|
|
def prepare_password(self):
|
|
"""Returns password as as clear text"""
|
|
if not self._password:
|
|
return b'\x00'
|
|
password = self._password
|
|
|
|
if PY2:
|
|
if isinstance(password, unicode): # pylint: disable=E0602
|
|
password = password.encode('utf8')
|
|
elif isinstance(password, str):
|
|
password = password.encode('utf8')
|
|
|
|
return password + b'\x00'
|
|
|
|
|
|
class MySQLSHA256PasswordAuthPlugin(BaseAuthPlugin):
|
|
"""Class implementing the MySQL SHA256 authentication plugin
|
|
|
|
Note that encrypting using RSA is not supported since the Python
|
|
Standard Library does not provide this OpenSSL functionality.
|
|
"""
|
|
|
|
requires_ssl = True
|
|
plugin_name = 'sha256_password'
|
|
|
|
def prepare_password(self):
|
|
"""Returns password as as clear text"""
|
|
if not self._password:
|
|
return b'\x00'
|
|
password = self._password
|
|
|
|
if PY2:
|
|
if isinstance(password, unicode): # pylint: disable=E0602
|
|
password = password.encode('utf8')
|
|
elif isinstance(password, str):
|
|
password = password.encode('utf8')
|
|
|
|
return password + b'\x00'
|
|
|
|
|
|
class MySQLCachingSHA2PasswordAuthPlugin(BaseAuthPlugin):
|
|
"""Class implementing the MySQL caching_sha2_password authentication plugin
|
|
|
|
Note that encrypting using RSA is not supported since the Python
|
|
Standard Library does not provide this OpenSSL functionality.
|
|
"""
|
|
requires_ssl = False
|
|
plugin_name = 'caching_sha2_password'
|
|
perform_full_authentication = 4
|
|
fast_auth_success = 3
|
|
|
|
def _scramble(self):
|
|
""" Returns a scramble of the password using a Nonce sent by the
|
|
server.
|
|
|
|
The scramble is of the form:
|
|
XOR(SHA2(password), SHA2(SHA2(SHA2(password)), Nonce))
|
|
"""
|
|
if not self._auth_data:
|
|
raise errors.InterfaceError("Missing authentication data (seed)")
|
|
|
|
if not self._password:
|
|
return b''
|
|
|
|
password = self._password.encode('utf-8') \
|
|
if isinstance(self._password, UNICODE_TYPES) else self._password
|
|
|
|
if PY2:
|
|
password = buffer(password) # pylint: disable=E0602
|
|
try:
|
|
auth_data = buffer(self._auth_data) # pylint: disable=E0602
|
|
except TypeError:
|
|
raise errors.InterfaceError("Authentication data incorrect")
|
|
else:
|
|
password = password
|
|
auth_data = self._auth_data
|
|
|
|
hash1 = sha256(password).digest()
|
|
hash2 = sha256()
|
|
hash2.update(sha256(hash1).digest())
|
|
hash2.update(auth_data)
|
|
hash2 = hash2.digest()
|
|
if PY2:
|
|
xored = [ord(h1) ^ ord(h2) for (h1, h2) in zip(hash1, hash2)]
|
|
else:
|
|
xored = [h1 ^ h2 for (h1, h2) in zip(hash1, hash2)]
|
|
hash3 = struct.pack('32B', *xored)
|
|
|
|
return hash3
|
|
|
|
def prepare_password(self):
|
|
if len(self._auth_data) > 1:
|
|
return self._scramble()
|
|
elif self._auth_data[0] == self.perform_full_authentication:
|
|
return self._full_authentication()
|
|
return None
|
|
|
|
def _full_authentication(self):
|
|
"""Returns password as as clear text"""
|
|
if not self._ssl_enabled:
|
|
raise errors.InterfaceError("{name} requires SSL".format(
|
|
name=self.plugin_name))
|
|
|
|
if not self._password:
|
|
return b'\x00'
|
|
password = self._password
|
|
|
|
if PY2:
|
|
if isinstance(password, unicode): # pylint: disable=E0602
|
|
password = password.encode('utf8')
|
|
elif isinstance(password, str):
|
|
password = password.encode('utf8')
|
|
|
|
return password + b'\x00'
|
|
|
|
|
|
def get_auth_plugin(plugin_name):
|
|
"""Return authentication class based on plugin name
|
|
|
|
This function returns the class for the authentication plugin plugin_name.
|
|
The returned class is a subclass of BaseAuthPlugin.
|
|
|
|
Raises errors.NotSupportedError when plugin_name is not supported.
|
|
|
|
Returns subclass of BaseAuthPlugin.
|
|
"""
|
|
for authclass in BaseAuthPlugin.__subclasses__(): # pylint: disable=E1101
|
|
if authclass.plugin_name == plugin_name:
|
|
return authclass
|
|
|
|
raise errors.NotSupportedError(
|
|
"Authentication plugin '{0}' is not supported".format(plugin_name))
|