Skip to content

Commit

Permalink
Merge 5211ff9 into d110656
Browse files Browse the repository at this point in the history
  • Loading branch information
atodorov committed Mar 22, 2020
2 parents d110656 + 5211ff9 commit 49e3378
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/kerberos.yml
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Install Python dependencies
run: |
sudo apt-get install libkrb5-dev
pip install coverage kerberos
pip install coverage kerberos requests
- name: Build & start services
run: |
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -11,7 +11,7 @@ env:
- MAKE=build

install:
- pip install coverage pylint flake8 twine kerberos
- pip install coverage pylint flake8 twine kerberos requests

script:
- make $MAKE
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Expand Up @@ -31,6 +31,11 @@ run-services:
docker exec -i web_kiwitcms_org /Kiwi/manage.py migrate
docker exec -i web_kiwitcms_org /Kiwi/manage.py createsuperuser --noinput --username super-root --email root@example.com
cat tests/krb5/kiwitcms_kerberos/db_init.py | docker exec -i web_kiwitcms_org /Kiwi/manage.py shell
@echo "=== add the following to client's /etc/hosts & /etc/krb5.conf ==="
@echo "--- web.kiwitcms.org ---"
@docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web_kiwitcms_org
@echo "--- krb5.kiwitcms.org ---"
@docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' krb5_kiwitcms_org

.PHONY: verify-integration
verify-integration:
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Expand Up @@ -15,7 +15,7 @@ install:

- "python --version"
- "python -m pip install --disable-pip-version-check --user --upgrade pip wheel"
- "python -m pip install coverage pylint flake8 twine winkerberos"
- "python -m pip install coverage pylint flake8 twine winkerberos requests"

build: off

Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -21,6 +21,7 @@
url='https://github.com/kiwitcms/tcms-api',
python_requires='>=3.6',
install_requires=(
['requests'] +
["winkerberos"] if sys.platform.startswith("win") else ["kerberos"]
),
classifiers=[
Expand Down
9 changes: 8 additions & 1 deletion tcms_api/__init__.py
Expand Up @@ -36,7 +36,14 @@
username = your-username
password = your-password
For Kerberos specify ``use_kerberos = True`` key!
For Kerberos specify the ``use_kerberos = True`` key without username
and password! Also make sure that your ``/etc/krb5.conf`` contains::
[libdefaults]
default_realm = .EXAMPLE.COM
where ``EXAMPLE.COM`` matches the realm in your organization.
It's also possible to provide system-wide config in ``/etc/tcms.conf``.
Connect to backend::
Expand Down
48 changes: 41 additions & 7 deletions tcms_api/xmlrpc.py
Expand Up @@ -11,11 +11,15 @@
"""
# pylint: disable=too-few-public-methods

from http import HTTPStatus
from http.client import HTTPSConnection
from http.cookiejar import CookieJar
import urllib.parse
from xmlrpc.client import SafeTransport, Transport, ServerProxy
import sys

import requests

if sys.platform.startswith("win"):
import winkerberos as kerberos # pylint: disable=import-error
else:
Expand Down Expand Up @@ -66,7 +70,7 @@ def get_host_info(self, host):
kerberos.authGSSClientStep(context, "")

extra_headers = [
("Authorization", "negotiate %s" %
("Authorization", "Negotiate %s" %
kerberos.authGSSClientResponse(context))
]

Expand All @@ -92,21 +96,33 @@ def make_connection(self, host):
return self._connection[1]


def get_hostname(url):
"""
Performs the same parsing of the URL as the Transport
class and returns only the hostname which is used to
generate the service principal name for Kiwi TCMS and
the respective Authorize header!
"""
_type, uri = urllib.parse.splittype(url)
hostname, _path = urllib.parse.splithost(uri)
return hostname


class TCMSXmlrpc:
"""
TCMS XML-RPC client for server deployed without BASIC authentication.
"""
def __init__(self, username, password, url):
if url.startswith('https://'):
self._transport = SafeCookieTransport()
self.transport = SafeCookieTransport()
elif url.startswith('http://'):
self._transport = CookieTransport()
self.transport = CookieTransport()
else:
raise Exception("Unrecognized URL scheme")

self.server = ServerProxy(
url,
transport=self._transport,
transport=self.transport,
verbose=VERBOSE,
allow_none=1
)
Expand All @@ -120,10 +136,11 @@ class TCMSKerbXmlrpc(TCMSXmlrpc):
TCMSXmlrpc - TCMS XML-RPC client
for server deployed with mod_auth_kerb
"""
session_cookie_name = 'sessionid'

def __init__(self, url): # pylint: disable=super-init-not-called
if url.startswith('https://'):
self._transport = KerbTransport()
self.transport = KerbTransport()
elif url.startswith('http://'):
raise Exception("Encrypted https communication required for "
"Kerberos authentication."
Expand All @@ -133,10 +150,27 @@ def __init__(self, url): # pylint: disable=super-init-not-called

self.server = ServerProxy(
url,
transport=self._transport,
transport=self.transport,
verbose=VERBOSE,
allow_none=1
)

# Login, get a cookie into our cookie jar (login_dict):
self.server.Auth.login_krbv()
self.login(url)

def login(self, url):
url = url.replace('xml-rpc', 'login/kerberos')
hostname = get_hostname(url)

_, headers, _ = self.transport.get_host_info(hostname)
# transport returns list of tuples but requests needs a dictionary
headers = dict(headers)

# note: by default will follow redirects
with requests.sessions.Session() as session:
response = session.get(url, headers=headers)
assert response.status_code == HTTPStatus.OK
self.transport._cookies.append( # pylint: disable=protected-access
self.session_cookie_name + '=' +
session.cookies[self.session_cookie_name]
)
12 changes: 11 additions & 1 deletion tests/krb5/integration_test.py
Expand Up @@ -7,9 +7,11 @@

import ssl
import unittest
from unittest.mock import patch

from datetime import datetime

import requests
from tcms_api import TCMS


Expand All @@ -23,10 +25,18 @@
ssl._create_default_https_context = _create_unverified_https_context


class DoNotVerifySSLSession(requests.sessions.Session):
def get(self, url, **kwargs):
kwargs.setdefault('verify', False)
return super().get(url, **kwargs)


class IntegrationTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.rpc = TCMS().exec
with patch('requests.sessions.Session') as session:
session.return_value = DoNotVerifySSLSession()
cls.rpc = TCMS().exec

def test_readonly_filtering_works(self):
results = self.rpc.User.filter()
Expand Down
4 changes: 4 additions & 0 deletions tests/krb5/kiwitcms_kerberos/kerberos_settings.py
Expand Up @@ -9,6 +9,9 @@
}
}

if 'kiwitcms_kerberos' not in INSTALLED_APPS: # noqa: F821
INSTALLED_APPS.append('kiwitcms_kerberos') # noqa: F821

if 'social_django' not in INSTALLED_APPS: # noqa: F821
INSTALLED_APPS.append('social_django') # noqa: F821

Expand All @@ -29,6 +32,7 @@
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
'kiwitcms_kerberos.pipeline.initiate_defaults',
]

AUTHENTICATION_BACKENDS = [
Expand Down
6 changes: 6 additions & 0 deletions tests/krb5/kiwitcms_kerberos/pipeline.py
@@ -0,0 +1,6 @@
from tcms.utils.permissions import initiate_user_with_default_setups


def initiate_defaults(strategy, details, backend, user=None, *args, **kwargs):
if user and kwargs.get('is_new', False):
initiate_user_with_default_setups(user)

0 comments on commit 49e3378

Please sign in to comment.