Richard Nemeth
3 years ago
No known key found for this signature in database
GPG Key ID: 21C39470DF3DEC39
35 changed files with 2416 additions and 700 deletions
-
37.circleci/config.yml
-
32.github/workflows/bump.yaml
-
27.github/workflows/daily.yaml
-
90.github/workflows/lint.yaml
-
33.github/workflows/publish.yaml
-
1.gitignore
-
16.pre-commit-config.yaml
-
10.readthedocs.yaml
-
8.releaserc.json
-
29CHANGELOG.md
-
1CODEOWNERS
-
86CONTRIBUTING.md
-
3MANIFEST.in
-
5dev-requirements.txt
-
9docs-requirements.txt
-
66docs/source/conf.py
-
1keycloak/_version.py
-
53keycloak/authorization/__init__.py
-
5keycloak/authorization/policy.py
-
42keycloak/connection.py
-
26keycloak/exceptions.py
-
596keycloak/keycloak_admin.py
-
124keycloak/keycloak_openid.py
-
191keycloak/tests/test_connection.py
-
59keycloak/urls_patterns.py
-
6pyproject.toml
-
6requirements.txt
-
65setup.py
-
35test_keycloak_init.sh
-
0tests/__init__.py
-
61tests/conftest.py
-
1201tests/test_keycloak_admin.py
-
26tests/test_urls_patterns.py
-
4tox.env
-
48tox.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,32 @@ |
|||||
|
name: Bump version |
||||
|
|
||||
|
on: |
||||
|
workflow_run: |
||||
|
workflows: [ "Lint" ] |
||||
|
branches: [ master ] |
||||
|
types: |
||||
|
- completed |
||||
|
|
||||
|
jobs: |
||||
|
tag-version: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
with: |
||||
|
token: ${{ secrets.PAT_TOKEN }} |
||||
|
- uses: actions/setup-node@v3 |
||||
|
with: |
||||
|
node-version: 18 |
||||
|
- name: determine-version |
||||
|
run: | |
||||
|
VERSION=$(npx semantic-release --branches master --dry-run | { grep -i 'the next release version is' || test $? = 1; } | sed -E 's/.* ([[:digit:].]+)$/\1/') |
||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV |
||||
|
id: version |
||||
|
- uses: rickstaa/action-create-tag@v1 |
||||
|
continue-on-error: true |
||||
|
env: |
||||
|
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} |
||||
|
with: |
||||
|
tag: v${{ env.VERSION }} |
||||
|
message: "Releasing v${{ env.VERSION }}" |
||||
|
github_token: ${{ secrets.PAT_TOKEN }} |
@ -0,0 +1,27 @@ |
|||||
|
name: Daily check |
||||
|
|
||||
|
on: |
||||
|
schedule: |
||||
|
- cron: '0 4 * * *' |
||||
|
|
||||
|
jobs: |
||||
|
test: |
||||
|
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 }} |
||||
|
- uses: docker-practice/actions-setup-docker@master |
||||
|
- name: Install dependencies |
||||
|
run: | |
||||
|
python -m pip install --upgrade pip |
||||
|
python -m pip install tox |
||||
|
- name: Run tests |
||||
|
run: | |
||||
|
tox -e tests |
@ -0,0 +1,90 @@ |
|||||
|
name: Lint |
||||
|
|
||||
|
on: |
||||
|
push: |
||||
|
branches: [ master ] |
||||
|
pull_request: |
||||
|
branches: [ master ] |
||||
|
|
||||
|
jobs: |
||||
|
check-commits: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- uses: webiny/action-conventional-commits@v1.0.3 |
||||
|
|
||||
|
check-linting: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- name: Set up Python 3.10 |
||||
|
uses: actions/setup-python@v3 |
||||
|
with: |
||||
|
python-version: "3.10" |
||||
|
- name: Install dependencies |
||||
|
run: | |
||||
|
python -m pip install --upgrade pip |
||||
|
python -m pip install tox |
||||
|
- name: Check linting, formatting |
||||
|
run: | |
||||
|
tox -e check |
||||
|
|
||||
|
check-docs: |
||||
|
runs-on: ubuntu-latest |
||||
|
needs: |
||||
|
- check-commits |
||||
|
- check-linting |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- name: Set up Python 3.10 |
||||
|
uses: actions/setup-python@v3 |
||||
|
with: |
||||
|
python-version: "3.10" |
||||
|
- name: Install dependencies |
||||
|
run: | |
||||
|
python -m pip install --upgrade pip |
||||
|
python -m pip install tox |
||||
|
- name: Check documentation build |
||||
|
run: | |
||||
|
tox -e docs |
||||
|
|
||||
|
test: |
||||
|
runs-on: ubuntu-latest |
||||
|
strategy: |
||||
|
fail-fast: false |
||||
|
matrix: |
||||
|
python-version: ["3.7", "3.8", "3.9", "3.10"] |
||||
|
needs: |
||||
|
- check-commits |
||||
|
- check-linting |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- name: Set up Python ${{ matrix.python-version }} |
||||
|
uses: actions/setup-python@v3 |
||||
|
with: |
||||
|
python-version: ${{ matrix.python-version }} |
||||
|
- uses: docker-practice/actions-setup-docker@master |
||||
|
- name: Install dependencies |
||||
|
run: | |
||||
|
python -m pip install --upgrade pip |
||||
|
python -m pip install tox |
||||
|
- name: Run tests |
||||
|
run: | |
||||
|
tox -e tests |
||||
|
|
||||
|
build: |
||||
|
runs-on: ubuntu-latest |
||||
|
needs: test |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- name: Set up Python 3.10 |
||||
|
uses: actions/setup-python@v3 |
||||
|
with: |
||||
|
python-version: "3.10" |
||||
|
- name: Install dependencies |
||||
|
run: | |
||||
|
python -m pip install --upgrade pip |
||||
|
python -m pip install tox |
||||
|
- name: Run build |
||||
|
run: | |
||||
|
tox -e build |
@ -0,0 +1,33 @@ |
|||||
|
name: Publish |
||||
|
|
||||
|
on: |
||||
|
push: |
||||
|
tags: |
||||
|
- 'v*' |
||||
|
|
||||
|
jobs: |
||||
|
publish: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- name: Set up Python 3.10 |
||||
|
uses: actions/setup-python@v3 |
||||
|
with: |
||||
|
python-version: "3.10" |
||||
|
- name: Install dependencies |
||||
|
run: | |
||||
|
python -m pip install --upgrade pip |
||||
|
python -m pip install tox wheel twine |
||||
|
- name: Apply the tag version |
||||
|
run: | |
||||
|
version=${{ github.ref_name }} |
||||
|
sed -i 's/__version__ = .*/__version__ = "'${version:1}'"/' keycloak/_version.py |
||||
|
- name: Run build |
||||
|
run: | |
||||
|
tox -e build |
||||
|
- name: Publish to PyPi |
||||
|
env: |
||||
|
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} |
||||
|
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} |
||||
|
run: | |
||||
|
twine upload -u $TWINE_USERNAME -p $TWINE_PASSWORD dist/* |
@ -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,10 @@ |
|||||
|
version: 2 |
||||
|
|
||||
|
build: |
||||
|
os: "ubuntu-20.04" |
||||
|
tools: |
||||
|
python: "3.10" |
||||
|
|
||||
|
python: |
||||
|
install: |
||||
|
- requirements: docs-requirements.txt |
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"plugins": ["@semantic-release/commit-analyzer"], |
||||
|
"verifyConditions": false, |
||||
|
"npmPublish": false, |
||||
|
"publish": false, |
||||
|
"fail": false, |
||||
|
"success": false |
||||
|
} |
@ -1,45 +1,44 @@ |
|||||
Changelog |
|
||||
============ |
|
||||
|
# Changelog |
||||
|
|
||||
All notable changes to this project will be documented in this file. |
All notable changes to this project will be documented in this file. |
||||
|
|
||||
## [0.5.0] - 2017-08-21 |
## [0.5.0] - 2017-08-21 |
||||
|
|
||||
* Basic functions for Keycloak API (well_know, token, userinfo, logout, certs, |
|
||||
|
- Basic functions for Keycloak API (well_know, token, userinfo, logout, certs, |
||||
entitlement, instropect) |
entitlement, instropect) |
||||
|
|
||||
## [0.6.0] - 2017-08-23 |
## [0.6.0] - 2017-08-23 |
||||
|
|
||||
* Added load authorization settings |
|
||||
|
- Added load authorization settings |
||||
|
|
||||
## [0.7.0] - 2017-08-23 |
## [0.7.0] - 2017-08-23 |
||||
|
|
||||
* Added polices |
|
||||
|
- Added polices |
||||
|
|
||||
## [0.8.0] - 2017-08-23 |
## [0.8.0] - 2017-08-23 |
||||
|
|
||||
* Added permissions |
|
||||
|
- Added permissions |
||||
|
|
||||
## [0.9.0] - 2017-09-05 |
## [0.9.0] - 2017-09-05 |
||||
|
|
||||
* Added functions for Admin Keycloak API |
|
||||
|
- Added functions for Admin Keycloak API |
||||
|
|
||||
## [0.10.0] - 2017-10-23 |
## [0.10.0] - 2017-10-23 |
||||
|
|
||||
* Updated libraries versions |
|
||||
* Updated Docs |
|
||||
|
- Updated libraries versions |
||||
|
- Updated Docs |
||||
|
|
||||
## [0.11.0] - 2017-12-12 |
## [0.11.0] - 2017-12-12 |
||||
|
|
||||
* Changed Instropect RPT |
|
||||
|
- Changed Instropect RPT |
||||
|
|
||||
## [0.12.0] - 2018-01-25 |
## [0.12.0] - 2018-01-25 |
||||
|
|
||||
* Add groups functions |
|
||||
* Add Admin Tasks for user and client role management |
|
||||
* Function to trigger user sync from provider |
|
||||
|
- Add groups functions |
||||
|
- Add Admin Tasks for user and client role management |
||||
|
- Function to trigger user sync from provider |
||||
|
|
||||
## [0.12.1] - 2018-08-04 |
## [0.12.1] - 2018-08-04 |
||||
|
|
||||
* Add get_idps |
|
||||
* Rework group functions |
|
||||
|
- Add get_idps |
||||
|
- Rework group functions |
@ -0,0 +1 @@ |
|||||
|
* @ryshoooo @marcospereirampj |
@ -0,0 +1,86 @@ |
|||||
|
# Contributing |
||||
|
|
||||
|
Welcome to the Python Keycloak contributing guidelines. We are all more than happy to receive |
||||
|
any contributions to the repository and want to thank you in advance for your contributions! |
||||
|
This document outlines the process and the guidelines on how contributions work for this repository. |
||||
|
|
||||
|
## Setting up the dev environment |
||||
|
|
||||
|
The development environment is mainly up to the developer. Our recommendations are to create a python |
||||
|
virtual environment and install the necessary requirements. Example |
||||
|
|
||||
|
```sh |
||||
|
python -m venv venv |
||||
|
source venv/bin/activate |
||||
|
python -m pip install -U pip |
||||
|
python -m pip install -r requirements.txt |
||||
|
python -m pip install -r dev-requirements.txt |
||||
|
``` |
||||
|
|
||||
|
## Running checks and tests |
||||
|
|
||||
|
We're utilizing `tox` for most of the testing workflows. However we also have an external dependency on `docker`. |
||||
|
We're using docker to spin up a local keycloak instance which we run our test cases against. This is to avoid |
||||
|
a lot of unnecessary mocking and yet have immediate feedback from the actual Keycloak instance. All of the setup |
||||
|
is done for you with the tox environments, all you need is to have both tox and docker installed |
||||
|
(`tox` is included in the `dev-requirements.txt`). |
||||
|
|
||||
|
To run the unit tests, simply run |
||||
|
|
||||
|
```sh |
||||
|
tox -e tests |
||||
|
``` |
||||
|
|
||||
|
The project is also adhering to strict linting (flake8) and formatting (black + isort). You can always check that |
||||
|
your code changes adhere to the format by running |
||||
|
|
||||
|
```sh |
||||
|
tox -e check |
||||
|
``` |
||||
|
|
||||
|
If the check fails, you'll see an error message specifying what went wrong. To simplify things, you can also run |
||||
|
|
||||
|
```sh |
||||
|
tox -e apply-check |
||||
|
``` |
||||
|
|
||||
|
which will apply isort and black formatting for you in the repository. The flake8 problems however need to be resolved |
||||
|
manually by the developer. |
||||
|
|
||||
|
Additionally we require that the documentation pages are built without warnings. This check is also run via tox, using |
||||
|
the command |
||||
|
|
||||
|
```sh |
||||
|
tox -e docs |
||||
|
``` |
||||
|
|
||||
|
The check is also run in the CICD pipelines. We require that the documentation pages built from the code docstrings |
||||
|
do not create visually "bad" pages. |
||||
|
|
||||
|
## Conventional commits |
||||
|
|
||||
|
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 (this is included in the `dev-requirements.txt`) |
||||
|
|
||||
|
```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 |
||||
|
``` |
||||
|
|
||||
|
## How to contribute |
||||
|
|
||||
|
1. Fork this repository, develop and test your changes |
||||
|
2. Make sure that your changes do not decrease the test coverage |
||||
|
3. Make sure you're commits follow the conventional commits |
||||
|
4. Submit a pull request |
||||
|
|
||||
|
## How to release |
||||
|
|
||||
|
The CICD pipelines are set up for the repository. When a PR is merged, a new version of the library |
||||
|
will be automatically deployed to the PyPi server, meaning you'll be able to see your changes immediately. |
@ -1 +1,4 @@ |
|||||
include LICENSE |
include LICENSE |
||||
|
include requirements.txt |
||||
|
include dev-requirements.txt |
||||
|
include docs-requirements.txt |
@ -0,0 +1,5 @@ |
|||||
|
tox |
||||
|
pytest |
||||
|
pytest-cov |
||||
|
wheel |
||||
|
pre-commit |
@ -0,0 +1,9 @@ |
|||||
|
mock |
||||
|
alabaster |
||||
|
commonmark |
||||
|
recommonmark |
||||
|
sphinx |
||||
|
sphinx-rtd-theme |
||||
|
readthedocs-sphinx-ext |
||||
|
m2r2 |
||||
|
sphinx-autoapi |
@ -0,0 +1 @@ |
|||||
|
__version__ = "0.0.0" |
596
keycloak/keycloak_admin.py
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,191 +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 ..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,6 @@ |
|||||
|
[tool.black] |
||||
|
line-length = 99 |
||||
|
|
||||
|
[tool.isort] |
||||
|
line_length = 99 |
||||
|
profile = "black" |
@ -1,7 +1,3 @@ |
|||||
requests>=2.20.0 |
requests>=2.20.0 |
||||
httmock>=1.2.5 |
|
||||
python-jose>=1.4.0 |
python-jose>=1.4.0 |
||||
twine==1.13.0 |
|
||||
jose~=1.0.0 |
|
||||
setuptools~=54.2.0 |
|
||||
urllib3>=1.26.5 |
|
||||
|
urllib3>=1.26.0 |
@ -1,31 +1,56 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
import re |
||||
from setuptools import setup |
from setuptools import setup |
||||
|
|
||||
with open("README.md", "r") as fh: |
with open("README.md", "r") as fh: |
||||
long_description = fh.read() |
long_description = fh.read() |
||||
|
|
||||
|
with open("requirements.txt", "r") as fh: |
||||
|
reqs = fh.read().split("\n") |
||||
|
|
||||
|
with open("dev-requirements.txt", "r") as fh: |
||||
|
dev_reqs = fh.read().split("\n") |
||||
|
|
||||
|
with open("docs-requirements.txt", "r") as fh: |
||||
|
docs_reqs = fh.read().split("\n") |
||||
|
|
||||
|
|
||||
|
VERSIONFILE = "keycloak/_version.py" |
||||
|
verstrline = open(VERSIONFILE, "rt").read() |
||||
|
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" |
||||
|
mo = re.search(VSRE, verstrline, re.M) |
||||
|
if mo: |
||||
|
verstr = mo.group(1) |
||||
|
else: |
||||
|
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) |
||||
|
|
||||
setup( |
setup( |
||||
name='python-keycloak', |
|
||||
version='0.27.1', |
|
||||
url='https://github.com/marcospereirampj/python-keycloak', |
|
||||
license='The MIT License', |
|
||||
author='Marcos Pereira', |
|
||||
author_email='marcospereira.mpj@gmail.com', |
|
||||
keywords='keycloak openid', |
|
||||
description='python-keycloak is a Python package providing access to the Keycloak API.', |
|
||||
|
name="python-keycloak", |
||||
|
version=verstr, |
||||
|
url="https://github.com/marcospereirampj/python-keycloak", |
||||
|
license="The MIT License", |
||||
|
author="Marcos Pereira, Richard Nemeth", |
||||
|
author_email="marcospereira.mpj@gmail.com; ryshoooo@gmail.com", |
||||
|
keywords="keycloak openid oidc", |
||||
|
description="python-keycloak is a Python package providing access to the Keycloak API.", |
||||
long_description=long_description, |
long_description=long_description, |
||||
long_description_content_type="text/markdown", |
long_description_content_type="text/markdown", |
||||
packages=['keycloak', 'keycloak.authorization', 'keycloak.tests'], |
|
||||
install_requires=['requests>=2.20.0', 'python-jose>=1.4.0'], |
|
||||
tests_require=['httmock>=1.2.5'], |
|
||||
|
packages=["keycloak"], |
||||
|
install_requires=reqs, |
||||
|
tests_require=dev_reqs, |
||||
|
extras_require={"docs": docs_reqs}, |
||||
|
python_requires=">=3.7", |
||||
|
project_urls={ |
||||
|
"Documentation": "https://python-keycloak.readthedocs.io/en/latest/", |
||||
|
"Issue tracker": "https://github.com/marcospereirampj/python-keycloak/issues", |
||||
|
}, |
||||
classifiers=[ |
classifiers=[ |
||||
'Programming Language :: Python :: 3', |
|
||||
'License :: OSI Approved :: MIT License', |
|
||||
'Development Status :: 3 - Alpha', |
|
||||
'Operating System :: MacOS', |
|
||||
'Operating System :: Unix', |
|
||||
'Operating System :: Microsoft :: Windows', |
|
||||
'Topic :: Utilities' |
|
||||
] |
|
||||
|
"Programming Language :: Python :: 3", |
||||
|
"License :: OSI Approved :: MIT License", |
||||
|
"Development Status :: 3 - Alpha", |
||||
|
"Operating System :: MacOS", |
||||
|
"Operating System :: Unix", |
||||
|
"Operating System :: Microsoft :: Windows", |
||||
|
"Topic :: Utilities", |
||||
|
], |
||||
) |
) |
@ -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:$KEYCLOAK_PORT; do |
||||
|
sleep 5; |
||||
|
if [ ${SECONDS} -gt 180 ]; then |
||||
|
echo "Timeout exceeded"; |
||||
|
exit 1; |
||||
|
fi |
||||
|
done |
||||
|
} |
||||
|
|
||||
|
# Ensuring that keycloak 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,61 @@ |
|||||
|
import os |
||||
|
import uuid |
||||
|
|
||||
|
import pytest |
||||
|
|
||||
|
from keycloak import KeycloakAdmin |
||||
|
|
||||
|
|
||||
|
@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() |
||||
|
|
||||
|
|
||||
|
@pytest.fixture |
||||
|
def admin(env): |
||||
|
return KeycloakAdmin( |
||||
|
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", |
||||
|
username=env.KEYCLOAK_ADMIN, |
||||
|
password=env.KEYCLOAK_ADMIN_PASSWORD, |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@pytest.fixture |
||||
|
def realm(admin: KeycloakAdmin) -> str: |
||||
|
realm_name = str(uuid.uuid4()) |
||||
|
admin.create_realm(payload={"realm": realm_name}) |
||||
|
yield realm_name |
||||
|
admin.delete_realm(realm_name=realm_name) |
||||
|
|
||||
|
|
||||
|
@pytest.fixture |
||||
|
def user(admin: KeycloakAdmin, realm: str) -> str: |
||||
|
admin.realm_name = realm |
||||
|
username = str(uuid.uuid4()) |
||||
|
user_id = admin.create_user(payload={"username": username, "email": f"{username}@test.test"}) |
||||
|
yield user_id |
||||
|
admin.delete_user(user_id=user_id) |
||||
|
|
||||
|
|
||||
|
@pytest.fixture |
||||
|
def group(admin: KeycloakAdmin, realm: str) -> str: |
||||
|
admin.realm_name = realm |
||||
|
group_name = str(uuid.uuid4()) |
||||
|
group_id = admin.create_group(payload={"name": group_name}) |
||||
|
yield group_id |
||||
|
admin.delete_group(group_id=group_id) |
||||
|
|
||||
|
|
||||
|
@pytest.fixture |
||||
|
def client(admin: KeycloakAdmin, realm: str) -> str: |
||||
|
admin.realm_name = realm |
||||
|
client = str(uuid.uuid4()) |
||||
|
client_id = admin.create_client(payload={"name": client, "clientId": client}) |
||||
|
yield client_id |
||||
|
admin.delete_client(client_id=client_id) |
1201
tests/test_keycloak_admin.py
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,26 @@ |
|||||
|
from keycloak import urls_patterns |
||||
|
|
||||
|
|
||||
|
def test_correctness_of_patterns(): |
||||
|
"""Test that there are no duplicate url patterns.""" |
||||
|
|
||||
|
# Test that the patterns are present |
||||
|
urls = [x for x in dir(urls_patterns) if not x.startswith("__")] |
||||
|
assert len(urls) >= 0 |
||||
|
|
||||
|
# Test that all patterns start with URL_ |
||||
|
for url in urls: |
||||
|
assert url.startswith("URL_"), f"The url pattern {url} does not begin with URL_" |
||||
|
|
||||
|
# Test that the patterns have unique names |
||||
|
seen_urls = list() |
||||
|
for url in urls: |
||||
|
assert url not in seen_urls, f"The url pattern {url} is present twice." |
||||
|
seen_urls.append(url) |
||||
|
|
||||
|
# Test that the pattern values are unique |
||||
|
seen_url_values = list() |
||||
|
for url in urls: |
||||
|
url_value = urls_patterns.__dict__[url] |
||||
|
assert url_value not in seen_url_values, f"The url {url} has a duplicate value {url_value}" |
||||
|
seen_url_values.append(url_value) |
@ -0,0 +1,4 @@ |
|||||
|
KEYCLOAK_ADMIN=admin |
||||
|
KEYCLOAK_ADMIN_PASSWORD=admin |
||||
|
KEYCLOAK_HOST={env:KEYCLOAK_HOST:localhost} |
||||
|
KEYCLOAK_PORT=8080 |
@ -0,0 +1,48 @@ |
|||||
|
[tox] |
||||
|
envlist = check, apply-check, docs, tests, build |
||||
|
|
||||
|
[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:apply-check] |
||||
|
deps = |
||||
|
black |
||||
|
isort |
||||
|
flake8 |
||||
|
commands = |
||||
|
black -C keycloak tests docs |
||||
|
black keycloak tests docs |
||||
|
isort keycloak tests docs |
||||
|
|
||||
|
[testenv:docs] |
||||
|
deps = |
||||
|
.[docs] |
||||
|
commands = |
||||
|
python -m sphinx -T -E -W -b html -d _build/doctrees -D language=en ./docs/source _build/html |
||||
|
|
||||
|
[testenv:tests] |
||||
|
setenv = file|tox.env |
||||
|
deps = |
||||
|
-rrequirements.txt |
||||
|
-rdev-requirements.txt |
||||
|
commands = |
||||
|
./test_keycloak_init.sh "pytest -vv --cov=keycloak --cov-report term-missing {posargs}" |
||||
|
|
||||
|
[testenv:build] |
||||
|
deps = |
||||
|
-rdev-requirements.txt |
||||
|
commands = |
||||
|
python setup.py sdist bdist_wheel |
||||
|
|
||||
|
[flake8] |
||||
|
max-line-length = 99 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue