Skip to content

Commit

Permalink
Add python sample
Browse files Browse the repository at this point in the history
  • Loading branch information
ZSPASOV committed Jun 28, 2024
1 parent b2e0618 commit 68b721f
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 2 deletions.
128 changes: 128 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: python

on:
push:
paths:
- "samples/python/**"
- ".github/workflows/python.yml"
- "restarter/**"
schedule:
- cron: '0 7 * * *'

env:
REGISTRY: ghcr.io
IMAGE_NAME: miracl/oidc-samples/samples/python
DOCKER_BUILD_CONTEXT: samples/python
SAMPLE_IMAGE: ghcr.io/miracl/oidc-samples/samples/python:${{ github.sha }}
PROXY_IMAGE: ghcr.io/miracl/oidc-samples/proxy:latest
TESTS_IMAGE: ghcr.io/miracl/oidc-samples/integration-tests:latest
CLIENT_ID: ${{ secrets.CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}

jobs:
requirements-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Python
run: pip3 install -r samples/python/requirements.txt

build-docker:
needs: requirements-python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=${{ github.ref == 'refs/heads/master' }}
tags: |
type=raw,value=${{ github.sha }}
- uses: docker/build-push-action@v2
with:
context: ${{ env.DOCKER_BUILD_CONTEXT }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

run-integration-tests:
needs: build-docker
runs-on: ubuntu-latest
steps:
- name: Pull images
run: |
docker pull --quiet $SAMPLE_IMAGE
docker pull --quiet $TESTS_IMAGE
- name: Start the sample
run: docker run
--detach
--network host
--publish 8000
--name sample
--env CLIENT_ID
--env CLIENT_SECRET
$SAMPLE_IMAGE
- name: Sleep for a seconds
run: sleep 3s
shell: bash
- name: Run integration tests
run: docker run
--network host
$TESTS_IMAGE
--client-id $CLIENT_ID
--client-secret $CLIENT_SECRET

run-integration-tests-with-proxy:
needs: build-docker
runs-on: ubuntu-latest
steps:
- name: Pull images
run: |
docker pull --quiet $PROXY_IMAGE
docker pull --quiet $SAMPLE_IMAGE
docker pull --quiet $TESTS_IMAGE
- name: Start the proxy
run: docker run
--detach
--network host
--publish 8080
$PROXY_IMAGE
- name: Checkout repository content
uses: actions/checkout@v2
- name: Run the restarter
run: go run ./restarter/main.go &
- name: Start the sample
run: docker run
--detach
--network host
--publish 8080
--name sample
--env CLIENT_ID
--env CLIENT_SECRET
--env PROXY_HOST=127.0.0.1
--env PROXY_PORT=8080
$SAMPLE_IMAGE
- name: Sleep for a seconds
run: sleep 3s
shell: bash
- name: Run integration tests
run: docker run
--network host
$TESTS_IMAGE
--client-id $CLIENT_ID
--client-secret $CLIENT_SECRET
--sample-name sample
--proxy-host 127.0.0.1
--proxy-port 8080
--skip-modify-tests false
4 changes: 2 additions & 2 deletions integration-tests/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ var options struct {
}

func TestMain(m *testing.M) {
flag.StringVar(&options.clientSecret, "client-secret", "HE1-0PF98Q6P5o-zJ_DWUWrt42fUJ7I8KKGMEvM7_6o", "the client secret for the portal app")
flag.StringVar(&options.clientID, "client-id", "p1rp9216dgkor", "the client id for the portal app")
flag.StringVar(&options.clientSecret, "client-secret", "", "the client secret for the portal app")
flag.StringVar(&options.clientID, "client-id", "", "the client id for the portal app")
flag.StringVar(&options.redirectURL, "redirect-url", "http://localhost:8000/login", "the redirect url from the portal app")
flag.StringVar(&options.apiURL, "api-url", "https://api.mpin.io", "the mpin api URL")
flag.StringVar(&options.sampleURL, "sample-url", "http://127.0.0.1:8000", "the sample URL")
Expand Down
17 changes: 17 additions & 0 deletions samples/python/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ARG PYTHON_VERSION=3.12.3
FROM ghcr.io/miracl/oidc-samples/proxy:latest as proxy

FROM python:${PYTHON_VERSION}-slim as base

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /src
COPY . .

COPY --from=proxy /src/ca.crt /usr/local/share/ca-certificates/ca.pem
RUN update-ca-certificates
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
python -m pip install -r requirements.txt
EXPOSE 8000
CMD python3 -m flask run --host=0.0.0.0 --port=8000
5 changes: 5 additions & 0 deletions samples/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# MIRACL Trust Python OIDC Integration Sample

This example integrates the
[oic](https://pypi.org/project/oic/) library with the
MIRACL Trust platform.
108 changes: 108 additions & 0 deletions samples/python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os
import random

from flask import Flask, request
from oic.oic import Client, OicClientSettings
from oic.oic.message import AuthorizationResponse, RegistrationResponse
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
from oic.utils.http_util import Redirect
from requests import Session
from werkzeug.middleware.proxy_fix import ProxyFix

state_storage = {}
session_storage = {}

proxy_host = os.environ.get("PROXY_HOST")
proxy_port = os.environ.get("PROXY_PORT")


def new_app():
app = Flask(__name__)

session = Session()
oic_settings = OicClientSettings()

if proxy_host is not None and proxy_port is not None:
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
session.proxies = {"https": f"{proxy_host}:{proxy_port}"}
oic_settings.verify_ssl=False
oic_settings.requests_session=session

client = Client(
client_authn_method=CLIENT_AUTHN_METHOD,
settings=(
oic_settings if proxy_host is not None and proxy_port is not None else None
),
)
configuration_discovery = client.provider_config("https://api.mpin.io/")
client.handle_provider_config(
configuration_discovery, configuration_discovery["issuer"]
)

info = {
"client_id": os.environ.get("CLIENT_ID"),
"client_secret": os.environ.get("CLIENT_SECRET"),
}
client_reg = RegistrationResponse(**info)

client.store_registration_info(client_reg)

return app, client


app, client = new_app()


@app.route("/")
def index():
state = str(random.randint(100000, 999999))
nonce = str(random.randint(100000, 999999))
state_storage[state] = {"state": state, "nonce": nonce}
args = {
"client_id": client.client_id,
"response_type": "code",
"scope": ["openid", "email", "profile"],
"nonce": state_storage[state]["nonce"],
"redirect_uri": "http://localhost:8000/login",
"state": state_storage[state]["state"],
}

auth_req = client.construct_AuthorizationRequest(request_args=args)
login_url = auth_req.request(client.authorization_endpoint)

session_id = request.args.get("session")
if session_id is None:
return Redirect(login_url)

user_info = session_storage[session_id]
return user_info.to_json()


@app.route("/login")
def login():
response = request.environ["QUERY_STRING"]
aresp = client.parse_response(
AuthorizationResponse, info=response, sformat="urlencoded"
)
code = aresp["code"]

assert aresp["state"] in state_storage
state_storage.pop(aresp["state"])

args = {"code": code}

client.do_access_token_request(
state=aresp["state"], request_args=args, authn_method="client_secret_basic"
)

userinfo = client.do_user_info_request(method="GET", state=aresp["state"])

session_id = str(random.randint(100000, 999999))
session_storage[session_id] = userinfo

return Redirect(f"/?session={session_id}")


if __name__ == "__main__":
app.debug = True
app.run(host="0.0.0.0", port=8000, debug=True)
39 changes: 39 additions & 0 deletions samples/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
annotated-types==0.7.0
astroid==3.2.2
black==24.4.2
blinker==1.8.2
certifi==2024.6.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
cryptography==42.0.8
defusedxml==0.7.1
dill==0.3.8
Flask==3.0.3
future==1.0.0
idna==3.7
isort==5.13.2
itsdangerous==2.2.0
Jinja2==3.1.4
Mako==1.3.5
MarkupSafe==2.1.5
mccabe==0.7.0
mypy-extensions==1.0.0
oic==1.7.0
packaging==24.1
pathspec==0.12.1
platformdirs==4.2.2
pycparser==2.22
pycryptodomex==3.20.0
pydantic==2.7.4
pydantic-settings==2.3.3
pydantic_core==2.18.4
pyjwkest==1.4.2
pylint==3.2.3
python-dotenv==1.0.1
requests==2.32.3
six==1.16.0
tomlkit==0.12.5
typing_extensions==4.12.2
urllib3==2.2.1
Werkzeug==3.0.3

0 comments on commit 68b721f

Please sign in to comment.