http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/phoenixdb/types.py ---------------------------------------------------------------------- diff --git a/python/phoenixdb/types.py b/python/phoenixdb/types.py deleted file mode 100644 index f41355a..0000000 --- a/python/phoenixdb/types.py +++ /dev/null @@ -1,202 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import time -import datetime -from decimal import Decimal -from phoenixdb.avatica.proto import common_pb2 - -__all__ = [ - 'Date', 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks', - 'Binary', 'STRING', 'BINARY', 'NUMBER', 'DATETIME', 'ROWID', 'BOOLEAN', - 'JAVA_CLASSES', 'JAVA_CLASSES_MAP', 'TypeHelper', -] - - -def Date(year, month, day): - """Constructs an object holding a date value.""" - return datetime.date(year, month, day) - - -def Time(hour, minute, second): - """Constructs an object holding a time value.""" - return datetime.time(hour, minute, second) - - -def Timestamp(year, month, day, hour, minute, second): - """Constructs an object holding a datetime/timestamp value.""" - return datetime.datetime(year, month, day, hour, minute, second) - - -def DateFromTicks(ticks): - """Constructs an object holding a date value from the given UNIX timestamp.""" - return Date(*time.localtime(ticks)[:3]) - - -def TimeFromTicks(ticks): - """Constructs an object holding a time value from the given UNIX timestamp.""" - return Time(*time.localtime(ticks)[3:6]) - - -def TimestampFromTicks(ticks): - """Constructs an object holding a datetime/timestamp value from the given UNIX timestamp.""" - return Timestamp(*time.localtime(ticks)[:6]) - - -def Binary(value): - """Constructs an object capable of holding a binary (long) string value.""" - return bytes(value) - - -def time_from_java_sql_time(n): - dt = datetime.datetime(1970, 1, 1) + datetime.timedelta(milliseconds=n) - return dt.time() - - -def time_to_java_sql_time(t): - return ((t.hour * 60 + t.minute) * 60 + t.second) * 1000 + t.microsecond // 1000 - - -def date_from_java_sql_date(n): - return datetime.date(1970, 1, 1) + datetime.timedelta(days=n) - - -def date_to_java_sql_date(d): - if isinstance(d, datetime.datetime): - d = d.date() - td = d - datetime.date(1970, 1, 1) - return td.days - - -def datetime_from_java_sql_timestamp(n): - return datetime.datetime(1970, 1, 1) + datetime.timedelta(milliseconds=n) - - -def datetime_to_java_sql_timestamp(d): - td = d - datetime.datetime(1970, 1, 1) - return td.microseconds // 1000 + (td.seconds + td.days * 24 * 3600) * 1000 - - -class ColumnType(object): - - def __init__(self, eq_types): - self.eq_types = tuple(eq_types) - self.eq_types_set = set(eq_types) - - def __eq__(self, other): - return other in self.eq_types_set - - def __cmp__(self, other): - if other in self.eq_types_set: - return 0 - if other < self.eq_types: - return 1 - else: - return -1 - - -STRING = ColumnType(['VARCHAR', 'CHAR']) -"""Type object that can be used to describe string-based columns.""" - -BINARY = ColumnType(['BINARY', 'VARBINARY']) -"""Type object that can be used to describe (long) binary columns.""" - -NUMBER = ColumnType([ - 'INTEGER', 'UNSIGNED_INT', 'BIGINT', 'UNSIGNED_LONG', 'TINYINT', 'UNSIGNED_TINYINT', - 'SMALLINT', 'UNSIGNED_SMALLINT', 'FLOAT', 'UNSIGNED_FLOAT', 'DOUBLE', 'UNSIGNED_DOUBLE', 'DECIMAL' -]) -"""Type object that can be used to describe numeric columns.""" - -DATETIME = ColumnType(['TIME', 'DATE', 'TIMESTAMP', 'UNSIGNED_TIME', 'UNSIGNED_DATE', 'UNSIGNED_TIMESTAMP']) -"""Type object that can be used to describe date/time columns.""" - -ROWID = ColumnType([]) -"""Only implemented for DB API 2.0 compatibility, not used.""" - -BOOLEAN = ColumnType(['BOOLEAN']) -"""Type object that can be used to describe boolean columns. This is a phoenixdb-specific extension.""" - - -# XXX ARRAY - -if sys.version_info[0] < 3: - _long = long # noqa: F821 -else: - _long = int - -JAVA_CLASSES = { - 'bool_value': [ - ('java.lang.Boolean', common_pb2.BOOLEAN, None, None), - ], - 'string_value': [ - ('java.lang.Character', common_pb2.CHARACTER, None, None), - ('java.lang.String', common_pb2.STRING, None, None), - ('java.math.BigDecimal', common_pb2.BIG_DECIMAL, str, Decimal), - ], - 'number_value': [ - ('java.lang.Integer', common_pb2.INTEGER, None, int), - ('java.lang.Short', common_pb2.SHORT, None, int), - ('java.lang.Long', common_pb2.LONG, None, _long), - ('java.lang.Byte', common_pb2.BYTE, None, int), - ('java.sql.Time', common_pb2.JAVA_SQL_TIME, time_to_java_sql_time, time_from_java_sql_time), - ('java.sql.Date', common_pb2.JAVA_SQL_DATE, date_to_java_sql_date, date_from_java_sql_date), - ('java.sql.Timestamp', common_pb2.JAVA_SQL_TIMESTAMP, datetime_to_java_sql_timestamp, datetime_from_java_sql_timestamp), - ], - 'bytes_value': [ - ('[B', common_pb2.BYTE_STRING, Binary, None), - ], - 'double_value': [ - # if common_pb2.FLOAT is used, incorrect values are sent - ('java.lang.Float', common_pb2.DOUBLE, float, float), - ('java.lang.Double', common_pb2.DOUBLE, float, float), - ] -} -"""Groups of Java classes.""" - -JAVA_CLASSES_MAP = dict((v[0], (k, v[1], v[2], v[3])) for k in JAVA_CLASSES for v in JAVA_CLASSES[k]) -"""Flips the available types to allow for faster lookup by Java class. - -This mapping should be structured as: - { - 'java.math.BigDecimal': ('string_value', common_pb2.BIG_DECIMAL, str, Decimal),), - ... - '<java class>': (<field_name>, <Rep enum>, <mutate_to function>, <cast_from function>), - } -""" - - -class TypeHelper(object): - @staticmethod - def from_class(klass): - """Retrieves a Rep and functions to cast to/from based on the Java class. - - :param klass: - The string of the Java class for the column or parameter. - - :returns: tuple ``(field_name, rep, mutate_to, cast_from)`` - WHERE - ``field_name`` is the attribute in ``common_pb2.TypedValue`` - ``rep`` is the common_pb2.Rep enum - ``mutate_to`` is the function to cast values into Phoenix values, if any - ``cast_from`` is the function to cast from the Phoenix value to the Python value, if any - - :raises: - NotImplementedError - """ - if klass not in JAVA_CLASSES_MAP: - raise NotImplementedError('type {} is not supported'.format(klass)) - - return JAVA_CLASSES_MAP[klass]
http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/.travis.sh ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/.travis.sh b/python/requests-kerberos/.travis.sh new file mode 100755 index 0000000..a1861d4 --- /dev/null +++ b/python/requests-kerberos/.travis.sh @@ -0,0 +1,202 @@ +#!/bin/bash + +set -e + +IP_ADDRESS=$(hostname -I) +HOSTNAME=$(cat /etc/hostname) +PY_MAJOR=${PYENV:0:1} + +export KERBEROS_HOSTNAME=$HOSTNAME.$KERBEROS_REALM +export DEBIAN_FRONTEND=noninteractive + +echo "Configure the hosts file for Kerberos to work in a container" +cp /etc/hosts ~/hosts.new +sed -i "/.*$HOSTNAME/c\\$IP_ADDRESS\t$KERBEROS_HOSTNAME" ~/hosts.new +cp -f ~/hosts.new /etc/hosts + +echo "Setting up Kerberos config file at /etc/krb5.conf" +cat > /etc/krb5.conf << EOL +[libdefaults] + default_realm = ${KERBEROS_REALM^^} + dns_lookup_realm = false + dns_lookup_kdc = false + +[realms] + ${KERBEROS_REALM^^} = { + kdc = $KERBEROS_HOSTNAME + admin_server = $KERBEROS_HOSTNAME + } + +[domain_realm] + .$KERBEROS_REALM = ${KERBEROS_REALM^^} + +[logging] + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmin.log + default = FILE:/var/log/krb5lib.log +EOL + +echo "Setting up kerberos ACL configuration at /etc/krb5kdc/kadm5.acl" +mkdir /etc/krb5kdc +echo -e "*/*@${KERBEROS_REALM^^}\t*" > /etc/krb5kdc/kadm5.acl + +echo "Installing all the packages required in this test" +apt-get update +apt-get \ + -y \ + -qq \ + install \ + krb5-{user,kdc,admin-server,multidev} \ + libkrb5-dev \ + wget \ + curl \ + apache2 \ + libapache2-mod-auth-gssapi \ + python-dev \ + libffi-dev \ + build-essential \ + libssl-dev \ + zlib1g-dev \ + libbz2-dev + +echo "Creating KDC database" +# krb5_newrealm returns non-0 return code as it is running in a container, ignore it for this command only +set +e +printf "$KERBEROS_PASSWORD\n$KERBEROS_PASSWORD" | krb5_newrealm +set -e + +echo "Creating principals for tests" +kadmin.local -q "addprinc -pw $KERBEROS_PASSWORD $KERBEROS_USERNAME" + +echo "Adding HTTP principal for Kerberos and create keytab" +kadmin.local -q "addprinc -randkey HTTP/$KERBEROS_HOSTNAME" +kadmin.local -q "ktadd -k /etc/krb5.keytab HTTP/$KERBEROS_HOSTNAME" +chmod 777 /etc/krb5.keytab + +echo "Restarting Kerberos KDS service" +service krb5-kdc restart + +echo "Add ServerName to Apache config" +grep -q -F "ServerName $KERBEROS_HOSTNAME" /etc/apache2/apache2.conf || echo "ServerName $KERBEROS_HOSTNAME" >> /etc/apache2/apache2.conf + +echo "Deleting default virtual host file" +rm /etc/apache2/sites-enabled/000-default.conf +rm /etc/apache2/sites-available/000-default.conf +rm /etc/apache2/sites-available/default-ssl.conf + +echo "Create website directory structure and pages" +mkdir -p /var/www/example.com/public_html +chmod -R 755 /var/www +echo "<html><head><title>Title</title></head><body>body mesage</body></html>" > /var/www/example.com/public_html/index.html + +echo "Create self signed certificate for HTTPS endpoint" +mkdir /etc/apache2/ssl +openssl req \ + -x509 \ + -nodes \ + -days 365 \ + -newkey rsa:2048 \ + -keyout /etc/apache2/ssl/https.key \ + -out /etc/apache2/ssl/https.crt \ + -subj "/CN=$KERBEROS_HOSTNAME/o=Testing LTS./C=US" + +echo "Create virtual host files" +cat > /etc/apache2/sites-available/example.com.conf << EOL +<VirtualHost *:80> + ServerName $KERBEROS_HOSTNAME + ServerAlias $KERBEROS_HOSTNAME + DocumentRoot /var/www/example.com/public_html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + <Directory "/var/www/example.com/public_html"> + AuthType GSSAPI + AuthName "GSSAPI Single Sign On Login" + Require user $KERBEROS_USERNAME@${KERBEROS_REALM^^} + GssapiCredStore keytab:/etc/krb5.keytab + </Directory> +</VirtualHost> +<VirtualHost *:443> + ServerName $KERBEROS_HOSTNAME + ServerAlias $KERBEROS_HOSTNAME + DocumentRoot /var/www/example.com/public_html + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + SSLEngine on + SSLCertificateFile /etc/apache2/ssl/https.crt + SSLCertificateKeyFile /etc/apache2/ssl/https.key + <Directory "/var/www/example.com/public_html"> + AuthType GSSAPI + AuthName "GSSAPI Single Sign On Login" + Require user $KERBEROS_USERNAME@${KERBEROS_REALM^^} + GssapiCredStore keytab:/etc/krb5.keytab + </Directory> +</VirtualHost> +EOL + +echo "Enabling virtual host site" +a2enmod ssl +a2ensite example.com.conf +service apache2 restart + +echo "Getting ticket for Kerberos user" +echo -n "$KERBEROS_PASSWORD" | kinit "$KERBEROS_USERNAME@${KERBEROS_REALM^^}" + +echo "Try out the HTTP connection with curl" +CURL_OUTPUT=$(curl --negotiate -u : "http://$KERBEROS_HOSTNAME") + +if [ "$CURL_OUTPUT" != "<html><head><title>Title</title></head><body>body mesage</body></html>" ]; then + echo -e "ERROR: Did not get success message, cannot continue with actual tests:\nActual Output:\n$CURL_OUTPUT" + exit 1 +else + echo -e "SUCCESS: Apache site built and set for Kerberos auth\nActual Output:\n$CURL_OUTPUT" +fi + +echo "Try out the HTTPS connection with curl" +CURL_OUTPUT=$(curl --negotiate -u : "https://$KERBEROS_HOSTNAME" --insecure) + +if [ "$CURL_OUTPUT" != "<html><head><title>Title</title></head><body>body mesage</body></html>" ]; then + echo -e "ERROR: Did not get success message, cannot continue with actual tests:\nActual Output:\n$CURL_OUTPUT" + exit 1 +else + echo -e "SUCCESS: Apache site built and set for Kerberos auth\nActual Output:\n$CURL_OUTPUT" +fi + +if [ "$IMAGE" == "ubuntu:16.04" ]; then + echo "Downloading Python $PYENV" + wget -q "https://www.python.org/ftp/python/$PYENV/Python-$PYENV.tgz" + tar xzf "Python-$PYENV.tgz" + cd "Python-$PYENV" + + echo "Configuring Python install" + ./configure &> /dev/null + + echo "Running make install on Python" + make install &> /dev/null + cd .. + rm -rf "Python-$PYENV" + rm "Python-$PYENV.tgz" +fi + +echo "Installing Pip" +wget -q https://bootstrap.pypa.io/get-pip.py +python$PY_MAJOR get-pip.py +rm get-pip.py + +echo "Updating pip and installing library" +pip$PY_MAJOR install -U pip setuptools +pip$PY_MAJOR install . +pip$PY_MAJOR install -r requirements-test.txt + +echo "Outputting build info before tests" +echo "Python Version: $(python$PY_MAJOR --version 2>&1)" +echo "Pip Version: $(pip$PY_MAJOR --version)" +echo "Pip packages: $(pip$PY_MAJOR list)" + +echo "Running Python tests" +export KERBEROS_PRINCIPAL="$KERBEROS_USERNAME@${KERBEROS_REALM^^}" +export KERBEROS_URL="http://$KERBEROS_HOSTNAME" +python$PY_MAJOR -m pytest -v --cov=requests_kerberos\ + +echo "Running Python test over HTTPS for basic CBT test" +export KERBEROS_URL="https://$KERBEROS_HOSTNAME" +python$PY_MAJOR -m pytest -v --cov=requests_kerberos\ http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/.travis.yml ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/.travis.yml b/python/requests-kerberos/.travis.yml new file mode 100644 index 0000000..8253cb4 --- /dev/null +++ b/python/requests-kerberos/.travis.yml @@ -0,0 +1,36 @@ +sudo: required + +language: python + +services: +- docker + +os: linux +dist: trusty + +matrix: + include: + - env: PYENV=2.7.14 IMAGE=python:2.7.14-slim-stretch + - env: PYENV=3.3.7 IMAGE=ubuntu:16.04 + - env: PYENV=3.4.7 IMAGE=ubuntu:16.04 + - env: PYENV=3.5.4 IMAGE=ubuntu:16.04 + - env: PYENV=3.6.3 IMAGE=python:3.6.3-slim-stretch + +install: +- pip install coveralls # Need to have coveralls installed locally for after_success to run + +script: +- > + docker run + -v $(pwd):$(pwd) + -w $(pwd) + -e PYENV=$PYENV + -e IMAGE=$IMAGE + -e KERBEROS_USERNAME=administrator + -e KERBEROS_PASSWORD=Password01 + -e KERBEROS_REALM=example.com + $IMAGE + /bin/bash .travis.sh + +after_success: +- coveralls http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/AUTHORS ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/AUTHORS b/python/requests-kerberos/AUTHORS new file mode 100644 index 0000000..eae61ac --- /dev/null +++ b/python/requests-kerberos/AUTHORS @@ -0,0 +1,3 @@ +Michael Komitee +Jose Castro Leon +David Pursehouse http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/HISTORY.rst ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/HISTORY.rst b/python/requests-kerberos/HISTORY.rst new file mode 100644 index 0000000..44bfc4b --- /dev/null +++ b/python/requests-kerberos/HISTORY.rst @@ -0,0 +1,102 @@ +History +======= + +0.12.0: 2017-12-20 +------------------------ + +- Add support for channel binding tokens (assumes pykerberos support >= 1.2.1) +- Add support for kerberos message encryption (assumes pykerberos support >= 1.2.1) +- Misc CI/test fixes + +0.11.0: 2016-11-02 +------------------ + +- Switch dependency on Windows from kerberos-sspi/pywin32 to WinKerberos. + This brings Custom Principal support to Windows users. + +0.10.0: 2016-05-18 +------------------ + +- Make it possible to receive errors without having their contents and headers + stripped. +- Resolve a bug caused by passing the ``principal`` keyword argument to + kerberos-sspi on Windows. + +0.9.0: 2016-05-06 +----------------- + +- Support for principal, hostname, and realm override. + +- Added support for mutual auth. + +0.8.0: 2016-01-07 +----------------- + +- Support for Kerberos delegation. + +- Fixed problems declaring kerberos-sspi on Windows installs. + +0.7.0: 2015-05-04 +----------------- + +- Added Windows native authentication support by adding kerberos-sspi as an + alternative backend. + +- Prevent infinite recursion when a server returns 401 to an authorization + attempt. + +- Reduce the logging during successful responses. + +0.6.1: 2014-11-14 +----------------- + +- Fix HTTPKerberosAuth not to treat non-file as a file + +- Prevent infinite recursion when GSSErrors occurs + +0.6: 2014-11-04 +--------------- + +- Handle mutual authentication (see pull request 36_) + + All users should upgrade immediately. This has been reported to + oss-security_ and we are awaiting a proper CVE identifier. + + **Update**: We were issued CVE-2014-8650 + +- Distribute as a wheel. + +.. _36: https://github.com/requests/requests-kerberos/pull/36 +.. _oss-security: http://www.openwall.com/lists/oss-security/ + +0.5: 2014-05-14 +--------------- + +- Allow non-HTTP service principals with HTTPKerberosAuth using a new optional + argument ``service``. + +- Fix bug in ``setup.py`` on distributions where the ``compiler`` module is + not available. + +- Add test dependencies to ``setup.py`` so ``python setup.py test`` will work. + +0.4: 2013-10-26 +--------------- + +- Minor updates in the README +- Change requirements to depend on requests above 1.1.0 + +0.3: 2013-06-02 +--------------- + +- Work with servers operating on non-standard ports + +0.2: 2013-03-26 +--------------- + +- Not documented + +0.1: Never released +------------------- + +- Initial Release http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/LICENSE ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/LICENSE b/python/requests-kerberos/LICENSE new file mode 100644 index 0000000..581f115 --- /dev/null +++ b/python/requests-kerberos/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012 Kenneth Reitz + +Permission to use, copy, modify and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS-IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/MANIFEST.in ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/MANIFEST.in b/python/requests-kerberos/MANIFEST.in new file mode 100644 index 0000000..c5c480f --- /dev/null +++ b/python/requests-kerberos/MANIFEST.in @@ -0,0 +1,5 @@ +include requirements.txt +include README.rst +include LICENSE +include HISTORY.rst +include AUTHORS http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/README.rst ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/README.rst b/python/requests-kerberos/README.rst new file mode 100644 index 0000000..c9bac7c --- /dev/null +++ b/python/requests-kerberos/README.rst @@ -0,0 +1,173 @@ +requests Kerberos/GSSAPI authentication library +=============================================== + +.. image:: https://travis-ci.org/requests/requests-kerberos.svg?branch=master + :target: https://travis-ci.org/requests/requests-kerberos + +.. image:: https://coveralls.io/repos/github/requests/requests-kerberos/badge.svg?branch=master + :target: https://coveralls.io/github/requests/requests-kerberos?branch=master + +Requests is an HTTP library, written in Python, for human beings. This library +adds optional Kerberos/GSSAPI authentication support and supports mutual +authentication. Basic GET usage: + + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth + >>> r = requests.get("http://example.org", auth=HTTPKerberosAuth()) + ... + +The entire ``requests.api`` should be supported. + +Authentication Failures +----------------------- + +Client authentication failures will be communicated to the caller by returning +the 401 response. + +Mutual Authentication +--------------------- + +REQUIRED +^^^^^^^^ + +By default, ``HTTPKerberosAuth`` will require mutual authentication from the +server, and if a server emits a non-error response which cannot be +authenticated, a ``requests_kerberos.errors.MutualAuthenticationError`` will +be raised. If a server emits an error which cannot be authenticated, it will +be returned to the user but with its contents and headers stripped. If the +response content is more important than the need for mutual auth on errors, +(eg, for certain WinRM calls) the stripping behavior can be suppressed by +setting ``sanitize_mutual_error_response=False``: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth, REQUIRED + >>> kerberos_auth = HTTPKerberosAuth(mutual_authentication=REQUIRED, sanitize_mutual_error_response=False) + >>> r = requests.get("https://windows.example.org/wsman", auth=kerberos_auth) + ... + + +OPTIONAL +^^^^^^^^ + +If you'd prefer to not require mutual authentication, you can set your +preference when constructing your ``HTTPKerberosAuth`` object: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth, OPTIONAL + >>> kerberos_auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) + >>> r = requests.get("http://example.org", auth=kerberos_auth) + ... + +This will cause ``requests_kerberos`` to attempt mutual authentication if the +server advertises that it supports it, and cause a failure if authentication +fails, but not if the server does not support it at all. + +DISABLED +^^^^^^^^ + +While we don't recommend it, if you'd prefer to never attempt mutual +authentication, you can do that as well: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth, DISABLED + >>> kerberos_auth = HTTPKerberosAuth(mutual_authentication=DISABLED) + >>> r = requests.get("http://example.org", auth=kerberos_auth) + ... + +Preemptive Authentication +------------------------- + +``HTTPKerberosAuth`` can be forced to preemptively initiate the Kerberos +GSS exchange and present a Kerberos ticket on the initial request (and all +subsequent). By default, authentication only occurs after a +``401 Unauthorized`` response containing a Kerberos or Negotiate challenge +is received from the origin server. This can cause mutual authentication +failures for hosts that use a persistent connection (eg, Windows/WinRM), as +no Kerberos challenges are sent after the initial auth handshake. This +behavior can be altered by setting ``force_preemptive=True``: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth, REQUIRED + >>> kerberos_auth = HTTPKerberosAuth(mutual_authentication=REQUIRED, force_preemptive=True) + >>> r = requests.get("https://windows.example.org/wsman", auth=kerberos_auth) + ... + +Hostname Override +----------------- + +If communicating with a host whose DNS name doesn't match its +kerberos hostname (eg, behind a content switch or load balancer), +the hostname used for the Kerberos GSS exchange can be overridden by +setting the ``hostname_override`` arg: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth, REQUIRED + >>> kerberos_auth = HTTPKerberosAuth(hostname_override="internalhost.local") + >>> r = requests.get("https://externalhost.example.org/", auth=kerberos_auth) + ... + +Explicit Principal +------------------ + +``HTTPKerberosAuth`` normally uses the default principal (ie, the user for +whom you last ran ``kinit`` or ``kswitch``, or an SSO credential if +applicable). However, an explicit principal can be specified, which will +cause Kerberos to look for a matching credential cache for the named user. +This feature depends on OS support for collection-type credential caches, +as well as working principal support in PyKerberos (it is broken in many +builds). An explicit principal can be specified with the ``principal`` arg: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth, REQUIRED + >>> kerberos_auth = HTTPKerberosAuth(principal="user@REALM") + >>> r = requests.get("http://example.org", auth=kerberos_auth) + ... + +On Windows, WinKerberos is used instead of PyKerberos. WinKerberos allows the +use of arbitrary principals instead of a credential cache. Passwords can be +specified by following the form ``user@realm:password`` for ``principal``. + +Delegation +---------- + +``requests_kerberos`` supports credential delegation (``GSS_C_DELEG_FLAG``). +To enable delegation of credentials to a server that requests delegation, pass +``delegate=True`` to ``HTTPKerberosAuth``: + +.. code-block:: python + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth + >>> r = requests.get("http://example.org", auth=HTTPKerberosAuth(delegate=True)) + ... + +Be careful to only allow delegation to servers you trust as they will be able +to impersonate you using the delegated credentials. + +Logging +------- + +This library makes extensive use of Python's logging facilities. + +Log messages are logged to the ``requests_kerberos`` and +``requests_kerberos.kerberos_`` named loggers. + +If you are having difficulty we suggest you configure logging. Issues with the +underlying kerberos libraries will be made apparent. Additionally, copious debug +information is made available which may assist in troubleshooting if you +increase your log level all the way up to debug. http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/requests_kerberos/__init__.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/requests_kerberos/__init__.py b/python/requests-kerberos/requests_kerberos/__init__.py new file mode 100644 index 0000000..63c7db5 --- /dev/null +++ b/python/requests-kerberos/requests_kerberos/__init__.py @@ -0,0 +1,25 @@ +""" +requests Kerberos/GSSAPI authentication library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. This library +adds optional Kerberos/GSSAPI authentication support and supports mutual +authentication. Basic GET usage: + + >>> import requests + >>> from requests_kerberos import HTTPKerberosAuth + >>> r = requests.get("http://example.org", auth=HTTPKerberosAuth()) + +The entire `requests.api` should be supported. +""" +import logging + +from .kerberos_ import HTTPKerberosAuth, REQUIRED, OPTIONAL, DISABLED +from .exceptions import MutualAuthenticationError +from .compat import NullHandler + +logging.getLogger(__name__).addHandler(NullHandler()) + +__all__ = ('HTTPKerberosAuth', 'MutualAuthenticationError', 'REQUIRED', + 'OPTIONAL', 'DISABLED') +__version__ = '0.13.0.dev0' http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/requests_kerberos/compat.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/requests_kerberos/compat.py b/python/requests-kerberos/requests_kerberos/compat.py new file mode 100644 index 0000000..01b7500 --- /dev/null +++ b/python/requests-kerberos/requests_kerberos/compat.py @@ -0,0 +1,14 @@ +""" +Compatibility library for older versions of python +""" +import sys + +# python 2.7 introduced a NullHandler which we want to use, but to support +# older versions, we implement our own if needed. +if sys.version_info[:2] > (2, 6): + from logging import NullHandler +else: + from logging import Handler + class NullHandler(Handler): + def emit(self, record): + pass http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/requests_kerberos/exceptions.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/requests_kerberos/exceptions.py b/python/requests-kerberos/requests_kerberos/exceptions.py new file mode 100644 index 0000000..51e11ec --- /dev/null +++ b/python/requests-kerberos/requests_kerberos/exceptions.py @@ -0,0 +1,15 @@ +""" +requests_kerberos.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of exceptions. + +""" +from requests.exceptions import RequestException + + +class MutualAuthenticationError(RequestException): + """Mutual Authentication Error""" + +class KerberosExchangeError(RequestException): + """Kerberos Exchange Failed Error""" http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/requests_kerberos/kerberos_.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/requests_kerberos/kerberos_.py b/python/requests-kerberos/requests_kerberos/kerberos_.py new file mode 100644 index 0000000..38f9f76 --- /dev/null +++ b/python/requests-kerberos/requests_kerberos/kerberos_.py @@ -0,0 +1,457 @@ +try: + import kerberos +except ImportError: + import winkerberos as kerberos +import logging +import re +import sys +import warnings + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.exceptions import UnsupportedAlgorithm + +from requests.auth import AuthBase +from requests.models import Response +from requests.compat import urlparse, StringIO +from requests.structures import CaseInsensitiveDict +from requests.cookies import cookiejar_from_dict +from requests.packages.urllib3 import HTTPResponse + +from .exceptions import MutualAuthenticationError, KerberosExchangeError + +log = logging.getLogger(__name__) + +# Different types of mutual authentication: +# with mutual_authentication set to REQUIRED, all responses will be +# authenticated with the exception of errors. Errors will have their contents +# and headers stripped. If a non-error response cannot be authenticated, a +# MutualAuthenticationError exception will be raised. +# with mutual_authentication set to OPTIONAL, mutual authentication will be +# attempted if supported, and if supported and failed, a +# MutualAuthenticationError exception will be raised. Responses which do not +# support mutual authentication will be returned directly to the user. +# with mutual_authentication set to DISABLED, mutual authentication will not be +# attempted, even if supported. +REQUIRED = 1 +OPTIONAL = 2 +DISABLED = 3 + + +class NoCertificateRetrievedWarning(Warning): + pass + +class UnknownSignatureAlgorithmOID(Warning): + pass + + +class SanitizedResponse(Response): + """The :class:`Response <Response>` object, which contains a server's + response to an HTTP request. + + This differs from `requests.models.Response` in that it's headers and + content have been sanitized. This is only used for HTTP Error messages + which do not support mutual authentication when mutual authentication is + required.""" + + def __init__(self, response): + super(SanitizedResponse, self).__init__() + self.status_code = response.status_code + self.encoding = response.encoding + self.raw = response.raw + self.reason = response.reason + self.url = response.url + self.request = response.request + self.connection = response.connection + self._content_consumed = True + + self._content = "" + self.cookies = cookiejar_from_dict({}) + self.headers = CaseInsensitiveDict() + self.headers['content-length'] = '0' + for header in ('date', 'server'): + if header in response.headers: + self.headers[header] = response.headers[header] + + +def _negotiate_value(response): + """Extracts the gssapi authentication token from the appropriate header""" + if hasattr(_negotiate_value, 'regex'): + regex = _negotiate_value.regex + else: + # There's no need to re-compile this EVERY time it is called. Compile + # it once and you won't have the performance hit of the compilation. + regex = re.compile('(?:.*,)*\s*Negotiate\s*([^,]*),?', re.I) + _negotiate_value.regex = regex + + authreq = response.headers.get('www-authenticate', None) + + if authreq: + match_obj = regex.search(authreq) + if match_obj: + return match_obj.group(1) + + return None + + +def _get_certificate_hash(certificate_der): + # https://tools.ietf.org/html/rfc5929#section-4.1 + cert = x509.load_der_x509_certificate(certificate_der, default_backend()) + + try: + hash_algorithm = cert.signature_hash_algorithm + except UnsupportedAlgorithm as ex: + warnings.warn("Failed to get signature algorithm from certificate, " + "unable to pass channel bindings: %s" % str(ex), UnknownSignatureAlgorithmOID) + return None + + # if the cert signature algorithm is either md5 or sha1 then use sha256 + # otherwise use the signature algorithm + if hash_algorithm.name in ['md5', 'sha1']: + digest = hashes.Hash(hashes.SHA256(), default_backend()) + else: + digest = hashes.Hash(hash_algorithm, default_backend()) + + digest.update(certificate_der) + certificate_hash = digest.finalize() + + return certificate_hash + + +def _get_channel_bindings_application_data(response): + """ + https://tools.ietf.org/html/rfc5929 4. The 'tls-server-end-point' Channel Binding Type + + Gets the application_data value for the 'tls-server-end-point' CBT Type. + This is ultimately the SHA256 hash of the certificate of the HTTPS endpoint + appended onto tls-server-end-point. This value is then passed along to the + kerberos library to bind to the auth response. If the socket is not an SSL + socket or the raw HTTP object is not a urllib3 HTTPResponse then None will + be returned and the Kerberos auth will use GSS_C_NO_CHANNEL_BINDINGS + + :param response: The original 401 response from the server + :return: byte string used on the application_data.value field on the CBT struct + """ + + application_data = None + raw_response = response.raw + + if isinstance(raw_response, HTTPResponse): + try: + if sys.version_info > (3, 0): + socket = raw_response._fp.fp.raw._sock + else: + socket = raw_response._fp.fp._sock + except AttributeError: + warnings.warn("Failed to get raw socket for CBT; has urllib3 impl changed", + NoCertificateRetrievedWarning) + else: + try: + server_certificate = socket.getpeercert(True) + except AttributeError: + pass + else: + certificate_hash = _get_certificate_hash(server_certificate) + application_data = b'tls-server-end-point:' + certificate_hash + else: + warnings.warn( + "Requests is running with a non urllib3 backend, cannot retrieve server certificate for CBT", + NoCertificateRetrievedWarning) + + return application_data + +class HTTPKerberosAuth(AuthBase): + """Attaches HTTP GSSAPI/Kerberos Authentication to the given Request + object.""" + def __init__( + self, mutual_authentication=REQUIRED, + service="HTTP", delegate=False, force_preemptive=False, + principal=None, hostname_override=None, mech_oid=None, + sanitize_mutual_error_response=True, send_cbt=True): + self.context = {} + self.mutual_authentication = mutual_authentication + self.delegate = delegate + self.pos = None + self.service = service + self.force_preemptive = force_preemptive + self.principal = principal + self.hostname_override = hostname_override + self.mech_oid = mech_oid + self.sanitize_mutual_error_response = sanitize_mutual_error_response + self.auth_done = False + self.winrm_encryption_available = hasattr(kerberos, 'authGSSWinRMEncryptMessage') + + # Set the CBT values populated after the first response + self.send_cbt = send_cbt + self.cbt_binding_tried = False + self.cbt_struct = None + + def generate_request_header(self, response, host, is_preemptive=False): + """ + Generates the GSSAPI authentication token with kerberos. + + If any GSSAPI step fails, raise KerberosExchangeError + with failure detail. + + """ + + # Flags used by kerberos module. + gssflags = kerberos.GSS_C_MUTUAL_FLAG | kerberos.GSS_C_SEQUENCE_FLAG + if self.delegate: + gssflags |= kerberos.GSS_C_DELEG_FLAG + + try: + kerb_stage = "authGSSClientInit()" + # contexts still need to be stored by host, but hostname_override + # allows use of an arbitrary hostname for the kerberos exchange + # (eg, in cases of aliased hosts, internal vs external, CNAMEs + # w/ name-based HTTP hosting) + kerb_host = self.hostname_override if self.hostname_override is not None else host + kerb_spn = "{0}@{1}".format(self.service, kerb_host) + + if self.mech_oid is not None: + result, self.context[host] = kerberos.authGSSClientInit(kerb_spn, + gssflags=gssflags, principal=self.principal, mech_oid=self.mech_oid) + else: + result, self.context[host] = kerberos.authGSSClientInit(kerb_spn, + gssflags=gssflags, principal=self.principal) + + if result < 1: + raise EnvironmentError(result, kerb_stage) + + # if we have a previous response from the server, use it to continue + # the auth process, otherwise use an empty value + negotiate_resp_value = '' if is_preemptive else _negotiate_value(response) + + kerb_stage = "authGSSClientStep()" + # If this is set pass along the struct to Kerberos + if self.cbt_struct: + result = kerberos.authGSSClientStep(self.context[host], + negotiate_resp_value, + channel_bindings=self.cbt_struct) + else: + result = kerberos.authGSSClientStep(self.context[host], + negotiate_resp_value) + + if result < 0: + raise EnvironmentError(result, kerb_stage) + + kerb_stage = "authGSSClientResponse()" + gss_response = kerberos.authGSSClientResponse(self.context[host]) + + return "Negotiate {0}".format(gss_response) + + except kerberos.GSSError as error: + log.exception( + "generate_request_header(): {0} failed:".format(kerb_stage)) + log.exception(error) + raise KerberosExchangeError("%s failed: %s" % (kerb_stage, str(error.args))) + + except EnvironmentError as error: + # ensure we raised this for translation to KerberosExchangeError + # by comparing errno to result, re-raise if not + if error.errno != result: + raise + message = "{0} failed, result: {1}".format(kerb_stage, result) + log.error("generate_request_header(): {0}".format(message)) + raise KerberosExchangeError(message) + + def authenticate_user(self, response, **kwargs): + """Handles user authentication with gssapi/kerberos""" + + host = urlparse(response.url).hostname + + try: + auth_header = self.generate_request_header(response, host) + except KerberosExchangeError: + # GSS Failure, return existing response + return response + + log.debug("authenticate_user(): Authorization header: {0}".format( + auth_header)) + response.request.headers['Authorization'] = auth_header + + # Consume the content so we can reuse the connection for the next + # request. + response.content + response.raw.release_conn() + + _r = response.connection.send(response.request, **kwargs) + _r.history.append(response) + + log.debug("authenticate_user(): returning {0}".format(_r)) + return _r + + def handle_401(self, response, **kwargs): + """Handles 401's, attempts to use gssapi/kerberos authentication""" + + log.debug("handle_401(): Handling: 401") + if _negotiate_value(response) is not None: + _r = self.authenticate_user(response, **kwargs) + log.debug("handle_401(): returning {0}".format(_r)) + return _r + else: + log.debug("handle_401(): Kerberos is not supported") + log.debug("handle_401(): returning {0}".format(response)) + return response + + def handle_other(self, response): + """Handles all responses with the exception of 401s. + + This is necessary so that we can authenticate responses if requested""" + + log.debug("handle_other(): Handling: %d" % response.status_code) + + if self.mutual_authentication in (REQUIRED, OPTIONAL) and not self.auth_done: + + is_http_error = response.status_code >= 400 + + if _negotiate_value(response) is not None: + log.debug("handle_other(): Authenticating the server") + if not self.authenticate_server(response): + # Mutual authentication failure when mutual auth is wanted, + # raise an exception so the user doesn't use an untrusted + # response. + log.error("handle_other(): Mutual authentication failed") + raise MutualAuthenticationError("Unable to authenticate " + "{0}".format(response)) + + # Authentication successful + log.debug("handle_other(): returning {0}".format(response)) + self.auth_done = True + return response + + elif is_http_error or self.mutual_authentication == OPTIONAL: + if not response.ok: + log.error("handle_other(): Mutual authentication unavailable " + "on {0} response".format(response.status_code)) + + if(self.mutual_authentication == REQUIRED and + self.sanitize_mutual_error_response): + return SanitizedResponse(response) + else: + return response + else: + # Unable to attempt mutual authentication when mutual auth is + # required, raise an exception so the user doesn't use an + # untrusted response. + log.error("handle_other(): Mutual authentication failed") + raise MutualAuthenticationError("Unable to authenticate " + "{0}".format(response)) + else: + log.debug("handle_other(): returning {0}".format(response)) + return response + + def authenticate_server(self, response): + """ + Uses GSSAPI to authenticate the server. + + Returns True on success, False on failure. + """ + + log.debug("authenticate_server(): Authenticate header: {0}".format( + _negotiate_value(response))) + + host = urlparse(response.url).hostname + + try: + # If this is set pass along the struct to Kerberos + if self.cbt_struct: + result = kerberos.authGSSClientStep(self.context[host], + _negotiate_value(response), + channel_bindings=self.cbt_struct) + else: + result = kerberos.authGSSClientStep(self.context[host], + _negotiate_value(response)) + except kerberos.GSSError: + log.exception("authenticate_server(): authGSSClientStep() failed:") + return False + + if result < 1: + log.error("authenticate_server(): authGSSClientStep() failed: " + "{0}".format(result)) + return False + + log.debug("authenticate_server(): returning {0}".format(response)) + return True + + def handle_response(self, response, **kwargs): + """Takes the given response and tries kerberos-auth, as needed.""" + num_401s = kwargs.pop('num_401s', 0) + + # Check if we have already tried to get the CBT data value + if not self.cbt_binding_tried and self.send_cbt: + # If we haven't tried, try getting it now + cbt_application_data = _get_channel_bindings_application_data(response) + if cbt_application_data: + # Only the latest version of pykerberos has this method available + try: + self.cbt_struct = kerberos.channelBindings(application_data=cbt_application_data) + except AttributeError: + # Using older version set to None + self.cbt_struct = None + # Regardless of the result, set tried to True so we don't waste time next time + self.cbt_binding_tried = True + + if self.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + response.request.body.seek(self.pos) + + if response.status_code == 401 and num_401s < 2: + # 401 Unauthorized. Handle it, and if it still comes back as 401, + # that means authentication failed. + _r = self.handle_401(response, **kwargs) + log.debug("handle_response(): returning %s", _r) + log.debug("handle_response() has seen %d 401 responses", num_401s) + num_401s += 1 + return self.handle_response(_r, num_401s=num_401s, **kwargs) + elif response.status_code == 401 and num_401s >= 2: + # Still receiving 401 responses after attempting to handle them. + # Authentication has failed. Return the 401 response. + log.debug("handle_response(): returning 401 %s", response) + return response + else: + _r = self.handle_other(response) + log.debug("handle_response(): returning %s", _r) + return _r + + def deregister(self, response): + """Deregisters the response handler""" + response.request.deregister_hook('response', self.handle_response) + + def wrap_winrm(self, host, message): + if not self.winrm_encryption_available: + raise NotImplementedError("WinRM encryption is not available on the installed version of pykerberos") + + return kerberos.authGSSWinRMEncryptMessage(self.context[host], message) + + def unwrap_winrm(self, host, message, header): + if not self.winrm_encryption_available: + raise NotImplementedError("WinRM encryption is not available on the installed version of pykerberos") + + return kerberos.authGSSWinRMDecryptMessage(self.context[host], message, header) + + def __call__(self, request): + if self.force_preemptive and not self.auth_done: + # add Authorization header before we receive a 401 + # by the 401 handler + host = urlparse(request.url).hostname + + auth_header = self.generate_request_header(None, host, is_preemptive=True) + + log.debug("HTTPKerberosAuth: Preemptive Authorization header: {0}".format(auth_header)) + + request.headers['Authorization'] = auth_header + + request.register_hook('response', self.handle_response) + try: + self.pos = request.body.tell() + except AttributeError: + # In the case of HTTPKerberosAuth being reused and the body + # of the previous request was a file-like object, pos has + # the file position of the previous body. Ensure it's set to + # None. + self.pos = None + return request http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/requirements-test.txt ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/requirements-test.txt b/python/requests-kerberos/requirements-test.txt new file mode 100644 index 0000000..88c224f --- /dev/null +++ b/python/requests-kerberos/requirements-test.txt @@ -0,0 +1,4 @@ +mock +pytest<=3.2.5 +pytest-cov +coveralls http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/requirements.txt ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/requirements.txt b/python/requests-kerberos/requirements.txt new file mode 100644 index 0000000..ebb2686 --- /dev/null +++ b/python/requests-kerberos/requirements.txt @@ -0,0 +1,6 @@ +requests>=1.1.0 +winkerberos >= 0.5.0; sys.platform == 'win32' +pykerberos >= 1.1.8, < 1.2.0; sys.platform != 'win32' +cryptography>=1.3 +cryptography>=1.3; python_version!="3.3" +cryptography>=1.3, <2; python_version=="3.3" http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/setup.cfg ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/setup.cfg b/python/requests-kerberos/setup.cfg new file mode 100644 index 0000000..5e40900 --- /dev/null +++ b/python/requests-kerberos/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/setup.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/setup.py b/python/requests-kerberos/setup.py new file mode 100755 index 0000000..ee89ced --- /dev/null +++ b/python/requests-kerberos/setup.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# coding: utf-8 +import os +import re +from setuptools import setup + +path = os.path.dirname(__file__) +desc_fd = os.path.join(path, 'README.rst') +hist_fd = os.path.join(path, 'HISTORY.rst') + +long_desc = '' +short_desc = 'A Kerberos authentication handler for python-requests' + +if os.path.isfile(desc_fd): + with open(desc_fd) as fd: + long_desc = fd.read() + +if os.path.isfile(hist_fd): + with open(hist_fd) as fd: + long_desc = '\n\n'.join([long_desc, fd.read()]) + + +def get_version(): + """ + Simple function to extract the current version using regular expressions. + """ + reg = re.compile(r'__version__ = [\'"]([^\'"]*)[\'"]') + with open('requests_kerberos/__init__.py') as fd: + matches = list(filter(lambda x: x, map(reg.match, fd))) + + if not matches: + raise RuntimeError( + 'Could not find the version information for requests_kerberos' + ) + + return matches[0].group(1) + + +setup( + name='requests-kerberos', + description=short_desc, + long_description=long_desc, + author='Ian Cordasco, Cory Benfield, Michael Komitee', + author_email='graffatcolmin...@gmail.com', + url='https://github.com/requests/requests-kerberos', + packages=['requests_kerberos'], + package_data={'': ['LICENSE', 'AUTHORS']}, + include_package_data=True, + version=get_version(), + install_requires=[ + 'requests>=1.1.0', + 'cryptography>=1.3;python_version!="3.3"', + 'cryptography>=1.3,<2;python_version=="3.3"' + ], + extras_require={ + ':sys_platform=="win32"': ['winkerberos>=0.5.0'], + ':sys_platform!="win32"': ['pykerberos>=1.1.8,<1.2.0'], + }, + test_suite='test_requests_kerberos', + tests_require=['mock'], + classifiers=[ + "License :: OSI Approved :: ISC License (ISCL)" + ], +) http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/tests/__init__.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/tests/__init__.py b/python/requests-kerberos/tests/__init__.py new file mode 100644 index 0000000..e69de29 http://git-wip-us.apache.org/repos/asf/phoenix/blob/3cac9217/python/requests-kerberos/tests/test_functional_kerberos.py ---------------------------------------------------------------------- diff --git a/python/requests-kerberos/tests/test_functional_kerberos.py b/python/requests-kerberos/tests/test_functional_kerberos.py new file mode 100644 index 0000000..a3efef4 --- /dev/null +++ b/python/requests-kerberos/tests/test_functional_kerberos.py @@ -0,0 +1,47 @@ +import requests +import os +import unittest + +from requests_kerberos import HTTPKerberosAuth, REQUIRED + + +class KerberosFunctionalTestCase(unittest.TestCase): + """ + This test is designed to run functional tests against a live website + secured with Kerberos authentication. See .travis.sh for the script that + is used to setup a Kerberos realm and Apache site. + + For this test to run the 2 environment variables need to be set + KERBEROS_PRINCIPAL: The principal to authenticate with (u...@realm.com) + Before running this test you need to ensure you have gotten a valid + ticket for the user in that realm using kinit. + KERBEROS_URL: The URL (http://host.realm.com) to authenticate with + This need to be set up before hand + """ + + def setUp(self): + """Setup.""" + self.principal = os.environ.get('KERBEROS_PRINCIPAL', None) + self.url = os.environ.get('KERBEROS_URL', None) + + # Skip the test if not set + if self.principal is None: + raise unittest.SkipTest("KERBEROS_PRINCIPAL is not set, skipping functional tests") + if self.url is None: + raise unittest.SkipTest("KERBEROS_URL is not set, skipping functional tests") + + def test_successful_http_call(self): + session = requests.Session() + if self.url.startswith("https://"): + session.verify = False + + session.auth = HTTPKerberosAuth(mutual_authentication=REQUIRED, principal=self.principal) + request = requests.Request('GET', self.url) + prepared_request = session.prepare_request(request) + + response = session.send(prepared_request) + + assert response.status_code == 200, "HTTP response with kerberos auth did not return a 200 error code" + +if __name__ == '__main__': + unittest.main()