No known key found for this signature in database
GPG Key ID: 21C39470DF3DEC39
19 changed files with 213 additions and 280 deletions
-
37.circleci/config.yml
-
29.github/workflows/lint.yaml
-
2.gitignore
-
16.pre-commit-config.yaml
-
15CONTRIBUTING.md
-
66docs/source/conf.py
-
1docs/source/index.rst
-
2requirements.txt
-
4setup.py
-
35test_keycloak_init.sh
-
14tests/conftest.py
-
189tests/test_connection.py
-
9tests/test_keycloak_admin.py
-
4tox.env
-
32tox.ini
@ -1,37 +0,0 @@ |
|||
version: 2 |
|||
jobs: |
|||
build: |
|||
docker: |
|||
- image: circleci/python:3.6.1 |
|||
|
|||
working_directory: ~/repo |
|||
|
|||
steps: |
|||
- checkout |
|||
- restore_cache: |
|||
keys: |
|||
- v1-dependencies-{{ checksum "requirements.txt" }} |
|||
# fallback to using the latest cache if no exact match is found |
|||
- v1-dependencies- |
|||
|
|||
- run: |
|||
name: install dependencies |
|||
command: | |
|||
python3 -m venv venv |
|||
. venv/bin/activate |
|||
pip install -r requirements.txt |
|||
|
|||
- save_cache: |
|||
paths: |
|||
- ./venv |
|||
key: v1-dependencies-{{ checksum "requirements.txt" }} |
|||
|
|||
- run: |
|||
name: run tests |
|||
command: | |
|||
. venv/bin/activate |
|||
python3 -m unittest discover |
|||
|
|||
- store_artifacts: |
|||
path: test-reports |
|||
destination: test-reports |
|||
@ -0,0 +1,29 @@ |
|||
name: Python package linting |
|||
|
|||
on: |
|||
push: |
|||
branches: [ master ] |
|||
pull_request: |
|||
branches: [ master ] |
|||
|
|||
jobs: |
|||
build: |
|||
runs-on: ubuntu-latest |
|||
strategy: |
|||
fail-fast: false |
|||
matrix: |
|||
python-version: ["3.7", "3.8", "3.9", "3.10"] |
|||
|
|||
steps: |
|||
- uses: actions/checkout@v3 |
|||
- name: Set up Python ${{ matrix.python-version }} |
|||
uses: actions/setup-python@v3 |
|||
with: |
|||
python-version: ${{ matrix.python-version }} |
|||
- name: Install dependencies |
|||
run: | |
|||
python -m pip install --upgrade pip |
|||
python -m pip install tox |
|||
- name: Check linting, formatting |
|||
run: | |
|||
tox -e check |
|||
@ -0,0 +1,16 @@ |
|||
# See https://pre-commit.com for more information |
|||
# See https://pre-commit.com/hooks.html for more hooks |
|||
repos: |
|||
- repo: https://github.com/pre-commit/pre-commit-hooks |
|||
rev: v3.2.0 |
|||
hooks: |
|||
- id: trailing-whitespace |
|||
- id: end-of-file-fixer |
|||
- id: check-yaml |
|||
- id: check-added-large-files |
|||
- repo: https://github.com/compilerla/conventional-pre-commit |
|||
rev: v1.2.0 |
|||
hooks: |
|||
- id: conventional-pre-commit |
|||
stages: [ commit-msg ] |
|||
args: [ ] # optional: list of Conventional Commits types to allow |
|||
@ -0,0 +1,15 @@ |
|||
# Contributing |
|||
|
|||
Commits to this project must adhere to the [Conventional Commits |
|||
specification](https://www.conventionalcommits.org/en/v1.0.0/) that will allow |
|||
us to automate version bumps and changelog entry creation. |
|||
|
|||
After cloning this repository, you must install the pre-commit hook for |
|||
conventional commits: |
|||
|
|||
```sh |
|||
python3 -m venv .venv |
|||
source .venv/bin/activate |
|||
python3 -m pip install pre-commit |
|||
pre-commit install --install-hooks -t pre-commit -t pre-push -t commit-msg |
|||
``` |
|||
@ -0,0 +1,35 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
CMD_ARGS=$1 |
|||
KEYCLOAK_DOCKER_IMAGE="quay.io/keycloak/keycloak:latest" |
|||
|
|||
echo "${CMD_ARGS}" |
|||
|
|||
function keycloak_stop() { |
|||
docker stop unittest_keycloak &> /dev/null |
|||
docker rm unittest_keycloak &> /dev/null |
|||
} |
|||
|
|||
function keycloak_start() { |
|||
echo "Starting keycloak docker container" |
|||
docker run -d --name unittest_keycloak -e KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN}" -e KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" -p "${KEYCLOAK_PORT}:8080" "${KEYCLOAK_DOCKER_IMAGE}" start-dev |
|||
SECONDS=0 |
|||
until curl localhost:8080; do |
|||
sleep 5; |
|||
if [ ${SECONDS} -gt 180 ]; then |
|||
echo "Timeout exceeded"; |
|||
exit 1; |
|||
fi |
|||
done |
|||
} |
|||
|
|||
# Ensuring that postgres is stopped in case of CTRL-C |
|||
trap keycloak_stop err exit |
|||
|
|||
keycloak_stop # In case it did not shut down correctly last time. |
|||
keycloak_start |
|||
|
|||
eval ${CMD_ARGS} |
|||
RETURN_VALUE=$? |
|||
|
|||
exit ${RETURN_VALUE} |
|||
@ -0,0 +1,14 @@ |
|||
import os |
|||
|
|||
import pytest |
|||
|
|||
|
|||
@pytest.fixture |
|||
def env(): |
|||
class KeycloakTestEnv(object): |
|||
KEYCLOAK_HOST = os.environ["KEYCLOAK_HOST"] |
|||
KEYCLOAK_PORT = os.environ["KEYCLOAK_PORT"] |
|||
KEYCLOAK_ADMIN = os.environ["KEYCLOAK_ADMIN"] |
|||
KEYCLOAK_ADMIN_PASSWORD = os.environ["KEYCLOAK_ADMIN_PASSWORD"] |
|||
|
|||
return KeycloakTestEnv() |
|||
@ -1,189 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# |
|||
# Copyright (C) 2017 Marcos Pereira <marcospereira.mpj@gmail.com> |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Lesser General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# 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 Lesser General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Lesser General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
from unittest import mock |
|||
|
|||
from httmock import urlmatch, response, HTTMock, all_requests |
|||
|
|||
from keycloak import KeycloakAdmin, KeycloakOpenID |
|||
from keycloak.connection import ConnectionManager |
|||
|
|||
try: |
|||
import unittest |
|||
except ImportError: |
|||
import unittest2 as unittest |
|||
|
|||
|
|||
class TestConnection(unittest.TestCase): |
|||
def setUp(self): |
|||
self._conn = ConnectionManager(base_url="http://localhost/", headers={}, timeout=60) |
|||
|
|||
@all_requests |
|||
def response_content_success(self, url, request): |
|||
headers = {"content-type": "application/json"} |
|||
content = b"response_ok" |
|||
return response(200, content, headers, None, 5, request) |
|||
|
|||
def test_raw_get(self): |
|||
with HTTMock(self.response_content_success): |
|||
resp = self._conn.raw_get("/known_path") |
|||
self.assertEqual(resp.content, b"response_ok") |
|||
self.assertEqual(resp.status_code, 200) |
|||
|
|||
def test_raw_post(self): |
|||
@urlmatch(path="/known_path", method="post") |
|||
def response_post_success(url, request): |
|||
headers = {"content-type": "application/json"} |
|||
content = "response".encode("utf-8") |
|||
return response(201, content, headers, None, 5, request) |
|||
|
|||
with HTTMock(response_post_success): |
|||
resp = self._conn.raw_post("/known_path", {"field": "value"}) |
|||
self.assertEqual(resp.content, b"response") |
|||
self.assertEqual(resp.status_code, 201) |
|||
|
|||
def test_raw_put(self): |
|||
@urlmatch(netloc="localhost", path="/known_path", method="put") |
|||
def response_put_success(url, request): |
|||
headers = {"content-type": "application/json"} |
|||
content = "response".encode("utf-8") |
|||
return response(200, content, headers, None, 5, request) |
|||
|
|||
with HTTMock(response_put_success): |
|||
resp = self._conn.raw_put("/known_path", {"field": "value"}) |
|||
self.assertEqual(resp.content, b"response") |
|||
self.assertEqual(resp.status_code, 200) |
|||
|
|||
def test_raw_get_fail(self): |
|||
@urlmatch(netloc="localhost", path="/known_path", method="get") |
|||
def response_get_fail(url, request): |
|||
headers = {"content-type": "application/json"} |
|||
content = "404 page not found".encode("utf-8") |
|||
return response(404, content, headers, None, 5, request) |
|||
|
|||
with HTTMock(response_get_fail): |
|||
resp = self._conn.raw_get("/known_path") |
|||
|
|||
self.assertEqual(resp.content, b"404 page not found") |
|||
self.assertEqual(resp.status_code, 404) |
|||
|
|||
def test_raw_post_fail(self): |
|||
@urlmatch(netloc="localhost", path="/known_path", method="post") |
|||
def response_post_fail(url, request): |
|||
headers = {"content-type": "application/json"} |
|||
content = str(["Start can't be blank"]).encode("utf-8") |
|||
return response(404, content, headers, None, 5, request) |
|||
|
|||
with HTTMock(response_post_fail): |
|||
resp = self._conn.raw_post("/known_path", {"field": "value"}) |
|||
self.assertEqual(resp.content, str(["Start can't be blank"]).encode("utf-8")) |
|||
self.assertEqual(resp.status_code, 404) |
|||
|
|||
def test_raw_put_fail(self): |
|||
@urlmatch(netloc="localhost", path="/known_path", method="put") |
|||
def response_put_fail(url, request): |
|||
headers = {"content-type": "application/json"} |
|||
content = str(["Start can't be blank"]).encode("utf-8") |
|||
return response(404, content, headers, None, 5, request) |
|||
|
|||
with HTTMock(response_put_fail): |
|||
resp = self._conn.raw_put("/known_path", {"field": "value"}) |
|||
self.assertEqual(resp.content, str(["Start can't be blank"]).encode("utf-8")) |
|||
self.assertEqual(resp.status_code, 404) |
|||
|
|||
def test_add_param_headers(self): |
|||
self._conn.add_param_headers("test", "value") |
|||
self.assertEqual(self._conn.headers, {"test": "value"}) |
|||
|
|||
def test_del_param_headers(self): |
|||
self._conn.add_param_headers("test", "value") |
|||
self._conn.del_param_headers("test") |
|||
self.assertEqual(self._conn.headers, {}) |
|||
|
|||
def test_clean_param_headers(self): |
|||
self._conn.add_param_headers("test", "value") |
|||
self.assertEqual(self._conn.headers, {"test": "value"}) |
|||
self._conn.clean_headers() |
|||
self.assertEqual(self._conn.headers, {}) |
|||
|
|||
def test_exist_param_headers(self): |
|||
self._conn.add_param_headers("test", "value") |
|||
self.assertTrue(self._conn.exist_param_headers("test")) |
|||
self.assertFalse(self._conn.exist_param_headers("test_no")) |
|||
|
|||
def test_get_param_headers(self): |
|||
self._conn.add_param_headers("test", "value") |
|||
self.assertTrue(self._conn.exist_param_headers("test")) |
|||
self.assertFalse(self._conn.exist_param_headers("test_no")) |
|||
|
|||
def test_get_headers(self): |
|||
self._conn.add_param_headers("test", "value") |
|||
self.assertEqual(self._conn.headers, {"test": "value"}) |
|||
|
|||
def test_KeycloakAdmin_custom_header(self): |
|||
class FakeToken: |
|||
@staticmethod |
|||
def get(string_val): |
|||
return "faketoken" |
|||
|
|||
fake_token = FakeToken() |
|||
|
|||
with mock.patch.object( |
|||
KeycloakOpenID, "__init__", return_value=None |
|||
) as mock_keycloak_open_id: |
|||
with mock.patch( |
|||
"keycloak.keycloak_openid.KeycloakOpenID.token", return_value=fake_token |
|||
): |
|||
with mock.patch( |
|||
"keycloak.connection.ConnectionManager.__init__", return_value=None |
|||
) as mock_connection_manager: |
|||
with mock.patch( |
|||
"keycloak.connection.ConnectionManager.__del__", return_value=None |
|||
) as mock_connection_manager_delete: |
|||
server_url = "https://localhost/auth/" |
|||
username = "admin" |
|||
password = "secret" |
|||
realm_name = "master" |
|||
|
|||
headers = {"Custom": "test-custom-header"} |
|||
KeycloakAdmin( |
|||
server_url=server_url, |
|||
username=username, |
|||
password=password, |
|||
realm_name=realm_name, |
|||
verify=False, |
|||
custom_headers=headers, |
|||
) |
|||
|
|||
mock_keycloak_open_id.assert_called_with( |
|||
server_url=server_url, |
|||
realm_name=realm_name, |
|||
client_id="admin-cli", |
|||
client_secret_key=None, |
|||
verify=False, |
|||
custom_headers=headers, |
|||
) |
|||
|
|||
expected_header = { |
|||
"Authorization": "Bearer faketoken", |
|||
"Content-Type": "application/json", |
|||
"Custom": "test-custom-header", |
|||
} |
|||
|
|||
mock_connection_manager.assert_called_with( |
|||
base_url=server_url, headers=expected_header, timeout=60, verify=False |
|||
) |
|||
mock_connection_manager_delete.assert_called_once_with() |
|||
@ -0,0 +1,9 @@ |
|||
from keycloak import KeycloakAdmin |
|||
|
|||
|
|||
def test_keycloak_admin_init(env): |
|||
KeycloakAdmin( |
|||
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", |
|||
username=env.KEYCLOAK_ADMIN, |
|||
password=env.KEYCLOAK_ADMIN_PASSWORD, |
|||
) |
|||
@ -0,0 +1,4 @@ |
|||
KEYCLOAK_ADMIN=admin |
|||
KEYCLOAK_ADMIN_PASSWORD=admin |
|||
KEYCLOAK_HOST={env:KEYCLOAK_HOST:localhost} |
|||
KEYCLOAK_PORT=8080 |
|||
@ -0,0 +1,32 @@ |
|||
[tox] |
|||
envlist = check, docs, tests |
|||
|
|||
[testenv] |
|||
install_command = pip install {opts} {packages} |
|||
|
|||
[testenv:check] |
|||
deps = |
|||
black |
|||
isort |
|||
flake8 |
|||
commands = |
|||
black --check --diff keycloak tests docs |
|||
isort -c --df keycloak tests docs |
|||
flake8 keycloak tests docs |
|||
|
|||
[testenv:tests] |
|||
deps = |
|||
-rrequirements.txt |
|||
setenv = file|tox.env |
|||
commands = |
|||
./test_keycloak_init.sh "pytest -v --cov=keycloak --cov-report term-missing {posargs}" |
|||
|
|||
[flake8] |
|||
max-line-length = 99 |
|||
|
|||
[black] |
|||
line-length = 99 |
|||
|
|||
[isort] |
|||
line_length = 99 |
|||
profile = "black" |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue