Skip to content

Commit

Permalink
PHOENIX-5844 Feature parity for the python client (part 1: authentica…
Browse files Browse the repository at this point in the history
…tion rewrite)

resolves:
PHOENIX-5845 Add HTTPS support to the python client
PHOENIX-5847 Add BASIC auth support to the python client
PHOENIX-5848 Add DIGEST auth support to the python client
PHOENIX-5856 Switch the python driver to requests_gssapi

Closes apache#27
  • Loading branch information
stoty committed Apr 18, 2020
1 parent 027ee5e commit 9ab9344
Show file tree
Hide file tree
Showing 23 changed files with 74 additions and 2,116 deletions.
25 changes: 0 additions & 25 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,6 @@ EOF > test-client.py
$ python test-client.py
```

When using a PQS instance secured via SPNEGO with Kerberos-based authentication, you must also install the custom
release of requests-kerberos provided with PhoenixDB.


```bash
$ virtualenv e
$ source e/bin/activate
$ pip install file:///path/to/phoenix-x.y.z/phoenix/python/phoenixdb
$ pip install file:///path/to/phoenix-x.y.z/phoenix/python/requests-kerberos
$ cat <<EOF
import phoenixdb
import phoenixdb.cursor
if __name__ == '__main__':
database_url = 'http://localhost:8765/'
conn = phoenixdb.connect(database_url, autocommit=True, auth="SPNEGO")
cursor = conn.cursor()
cursor.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username VARCHAR)")
cursor.execute("UPSERT INTO users VALUES (?, ?)", (1, 'admin'))
cursor.execute("SELECT * FROM users")
print(cursor.fetchall())
EOF > test-client.py
$ python test-client.py
```

Please see the README included with PhoenixDB for more information on using the Python driver.

## Kerberos support in testing
Expand Down
8 changes: 8 additions & 0 deletions python/phoenixdb/NEWS.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

Unreleased
----------
- Replaced bundled requests_kerberos with request_gssapi library
- Refactor authentication code
- Support for specifying server certificate
- Support for BASIC and DIGEST authentication
- Fix HTTP error parsing

Version 0.7
-----------

Expand Down
58 changes: 52 additions & 6 deletions python/phoenixdb/phoenixdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
from phoenixdb.errors import * # noqa: F401,F403
from phoenixdb.types import * # noqa: F401,F403

from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL
from gssapi import mechs;
from requests.auth import HTTPBasicAuth, HTTPDigestAuth

__all__ = ['connect', 'apilevel', 'threadsafety', 'paramstyle'] + types.__all__ + errors.__all__


Expand All @@ -42,7 +46,8 @@
"""


def connect(url, max_retries=None, auth=None, **kwargs):
def connect(url, max_retries=None, auth=None, authentication=None, avatica_user=None, avatica_password=None,
truststore=None, verify=None, **kwargs):
"""Connects to a Phoenix query server.
:param url:
Expand All @@ -58,15 +63,56 @@ def connect(url, max_retries=None, auth=None, **kwargs):
The maximum number of retries in case there is a connection error.
:param cursor_factory:
If specified, the connection's :attr:`~phoenixdb.connection.Connection.cursor_factory` is set to it.
If specified, the connection's :attr:`~phoenixdb.connection.Connection.cursor_factory`
is set to it.
:param auth:
Authentication configuration object as expected by the underlying python_requests and
python_requests_gssapi library
:param authentication:
Alternative way to specify the authentication mechanism that mimics
the semantics of the JDBC drirver
:param avatica_user:
Username for BASIC or DIGEST authentication. Use in conjunction with the
`~authentication' option.
:param auth
If specified a specific auth type will be used, otherwise connection will be unauthenticated
Currently only SPNEGO is supported
:param avatica_password:
Password for BASIC or DIGEST authentication. Use in conjunction with the
`~authentication' option.
:param verify:
The path to the PEM file for verifying the server's certificate. It is passed directly to
the `~verify` parameter of the underlying python_requests library.
Setting it to false disables the server certificate verification.
:param truststore:
Alias for verify
:returns:
:class:`~phoenixdb.connection.Connection` object.
"""
client = AvaticaClient(url, max_retries=max_retries, auth=auth)

spnego = mechs.Mechanism.from_sasl_name("SPNEGO")

if auth == "SPNEGO":
#Special case for backwards compatibility
auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL, mech = spnego)
elif auth is None and authentication is not None:
if authentication == "SPNEGO":
auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL, mech = spnego)
elif authentication == "BASIC" and avatica_user is not None and avatica_password is not None:
auth = HTTPBasicAuth(avatica_user, avatica_password)
elif authentication == "DIGEST" and avatica_user is not None and avatica_password is not None:
auth = HTTPDigestAuth(avatica_user, avatica_password)

if verify is None and truststore is not None:
verify = truststore

client = AvaticaClient(url, max_retries=max_retries,
auth=auth,
verify=verify
)
client.connect()
return Connection(client, **kwargs)
23 changes: 13 additions & 10 deletions python/phoenixdb/phoenixdb/avatica/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
from phoenixdb.avatica.proto import requests_pb2, common_pb2, responses_pb2

import requests
#from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL
from requests_kerberos import HTTPKerberosAuth, OPTIONAL
import kerberos

try:
import urlparse
Expand Down Expand Up @@ -138,7 +135,7 @@ class AvaticaClient(object):
to a server using :func:`phoenixdb.connect`.
"""

def __init__(self, url, max_retries=None, auth=None):
def __init__(self, url, max_retries=None, auth=None, verify=None):
"""Constructs a new client object.
:param url:
Expand All @@ -147,6 +144,7 @@ def __init__(self, url, max_retries=None, auth=None):
self.url = parse_url(url)
self.max_retries = max_retries if max_retries is not None else 3
self.auth = auth
self.verify = verify
self.connection = None

def connect(self):
Expand All @@ -162,12 +160,17 @@ def _post_request(self, body, headers):
retry_count = self.max_retries
while True:
logger.debug("POST %s %r %r", self.url.geturl(), body, headers)

requestArgs = {'data':body, 'stream':True, 'headers':headers}

if self.auth is not None:
requestArgs.update(auth = self.auth)

if self.verify is not None:
requestArgs.update(verify = self.verify)

try:
if self.auth == "SPNEGO":
#response = requests.request('post', self.url.geturl(), data=body, stream=True, headers=headers, auth=HTTPSPNEGOAuth(mutual_authentication=OPTIONAL))
response = requests.request('post', self.url.geturl(), data=body, stream=True, headers=headers, auth=HTTPKerberosAuth(mutual_authentication=OPTIONAL, mech_oid=kerberos.GSS_MECH_OID_SPNEGO))
else:
response = requests.request('post', self.url.geturl(), data=body, stream=True, headers=headers)
response = requests.request('post', self.url.geturl(), **requestArgs)

except requests.HTTPError as e:
if retry_count > 0:
Expand Down Expand Up @@ -203,7 +206,7 @@ def _apply(self, request_data, expected_response_type=None):
if response.status_code != requests.codes.ok:
logger.debug("Received response\n%s", response_body)
if b'<html>' in response_body:
parse_error_page(response_body)
parse_error_page(response_body.decode(response.encoding))
else:
# assume the response is in protobuf format
parse_error_protobuf(response_body)
Expand Down
2 changes: 1 addition & 1 deletion python/phoenixdb/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ protobuf>=3.0.0
sphinx
flake8
requests
../requests-kerberos
requests-gssapi
202 changes: 0 additions & 202 deletions python/requests-kerberos/.travis.sh

This file was deleted.

Loading

0 comments on commit 9ab9344

Please sign in to comment.