Skip to content

Commit

Permalink
Merge edcd794 into 0a01291
Browse files Browse the repository at this point in the history
  • Loading branch information
hluk committed Jan 5, 2023
2 parents 0a01291 + edcd794 commit 248f93e
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 221 deletions.
292 changes: 120 additions & 172 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,19 @@ pytest-cov = {version = "^4.0.0", optional = true}
tox = {version = "^3.28.0", optional = true}
tox-docker = {version = "^3.1.0", optional = true}

flask-oidc = "^1.4.0"
Flask-SQLAlchemy = "^3.0.2"
SQLAlchemy = {version = "^1.4.39"}
psycopg2-binary = {version = "^2.9.3"}
alembic = "^1.8.1"
iso8601 = "^1.0.2"
Flask-Pydantic = "^0.11.0"

# https://github.com/puiterwijk/flask-oidc/issues/147
itsdangerous = {version = "==2.0.1", optional = true}

email-validator = "^1.3.0"
python-ldap = "^3.4.3"

Authlib = "^1.2.0"
requests = "^2.28.1"

[tool.poetry.extras]
test = [
"flake8",
Expand Down
77 changes: 57 additions & 20 deletions resultsdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,23 @@
# Josef Skladanka <jskladan@redhat.com>
# Ralph Bean <rbean@redhat.com>

import json
import logging
import logging.handlers
import logging.config as logging_config
import os

from authlib.integrations.flask_client import OAuth
from authlib.integrations.base_client import OAuthError
from flask import Flask, jsonify, session, url_for

from resultsdb.proxy import ReverseProxied
from resultsdb.controllers.main import main
from resultsdb.controllers.api_v2 import api as api_v2
from resultsdb.controllers.api_v3 import api as api_v3, oidc
from resultsdb.controllers.api_v3 import api as api_v3, create_endpoints
from resultsdb.models import db
from . import config

import flask


# the version as used in setup.py
__version__ = "2.2.0"
Expand All @@ -43,7 +46,7 @@


def create_app(config_obj=None):
app = flask.Flask(__name__)
app = Flask(__name__)
app.secret_key = "replace-me-with-something-random"

# make sure app behaves when behind a proxy
Expand Down Expand Up @@ -96,6 +99,14 @@ def create_app(config_obj=None):
db.init_app(app)

register_handlers(app)

if app.config["AUTH_MODULE"] == "oidc":
app.logger.info("OpenIDConnect authentication is enabled")
enable_oidc(app)
else:
app.logger.info("OpenIDConnect authentication is disabled")
app.oauth = None

register_blueprints(app)

app.logger.debug("Finished ResultsDB initialization")
Expand Down Expand Up @@ -156,30 +167,56 @@ def register_handlers(app):
# TODO: find out why error handler works for 404 but not for 400
@app.errorhandler(400)
def bad_request(error):
return flask.jsonify({"message": "Bad request"}), 400
return jsonify({"message": "Bad request"}), 400

@app.errorhandler(404)
def not_found(error):
return flask.jsonify({"message": "Not found"}), 404
return jsonify({"message": "Not found"}), 404


def register_blueprints(app):
app.register_blueprint(main)
app.register_blueprint(api_v2, url_prefix="/api/v2.0")
app.register_blueprint(api_v3, url_prefix="/api/v3")

if app.config["AUTH_MODULE"] == "oidc":

@app.route("/auth/oidclogin")
@oidc.require_login
def login():
return {
"username": oidc.user_getfield(app.config["OIDC_USERNAME_FIELD"]),
"token": oidc.get_access_token(),
}

oidc.init_app(app)
app.oidc = oidc
app.logger.info("OpenIDConnect authentication is enabled")
else:
app.logger.info("OpenIDConnect authentication is disabled")
def enable_oidc(app):
with open(app.config["OIDC_CLIENT_SECRETS"]) as client_secrets_file:
client_secrets = json.load(client_secrets_file)

provider = app.config.get("OIDC_PROVIDER", "web")
metadata = client_secrets[provider]
oauth = OAuth(app)
oauth.register(
"resultsdb",
client_id=metadata["client_id"],
client_secret=metadata["client_secret"],
server_metadata_url=app.config["OIDC_METADATA_URL"],
client_kwargs=app.config["OIDC_CLIENT_KWARGS"],
)

@app.route("/auth/oidclogin")
def login():
try:
token = oauth.resultsdb.authorize_access_token()
userinfo = token["userinfo"]
username = userinfo[app.config["OIDC_USERNAME_FIELD"]]
access_token = token["access_token"]
return jsonify(
{
"username": username,
"token": access_token,
}
)
except OAuthError:
redirect_uri = url_for("login", _external=True)
return oauth.resultsdb.authorize_redirect(redirect_uri)

@app.route("/auth/logout")
def logout():
session.pop("user", None)
return jsonify({"message": "Logged out"})

app.oauth = oauth

create_endpoints()
8 changes: 7 additions & 1 deletion resultsdb/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,12 @@ class Config(object):
AUTH_MODULE = None

OIDC_CLIENT_SECRETS = "/etc/resultsdb/oauth2_client_secrets.json"
OIDC_REQUIRED_SCOPE = "resultsdb_scope"
OIDC_METADATA_URL = "https://oauth.example.com/.well-known/openid-configuration"
OIDC_USERNAME_FIELD = "uid"
OIDC_SESSION_REFRESH_INTERVAL_SECONDS = 300
OIDC_SESSION_PERMANENT = False
OIDC_CLIENT_KWARGS = {"scope": "openid profile email"}
PERMANENT_SESSION_LIFETIME = 300

FEDMENU_URL = "https://apps.fedoraproject.org/fedmenu"
FEDMENU_DATA_URL = "https://apps.fedoraproject.org/js/data.js"
Expand Down Expand Up @@ -149,6 +153,8 @@ class TestingConfig(DevelopmentConfig):
}
]

OIDC_CLIENT_SECRETS = os.getcwd() + "/conf/oauth2_client_secrets.json.example"


def openshift_config(config_object, openshift_production):
# First, get db details from env
Expand Down
12 changes: 4 additions & 8 deletions resultsdb/controllers/api_v3.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: GPL-2.0+
from flask import Blueprint, jsonify, g, render_template
from flask import Blueprint, jsonify, render_template
from flask import current_app as app
from flask_oidc import OpenIDConnect
from flask_pydantic import validate

from resultsdb.models import db
Expand All @@ -20,7 +19,6 @@
)

api = Blueprint("api_v3", __name__)
oidc = OpenIDConnect()


def permissions():
Expand All @@ -34,7 +32,9 @@ def _verify_authorization(user, testcase):


def create_result(body: ResultParamsBase):
user = g.oidc_token_info[app.config["OIDC_USERNAME_FIELD"]]
token = app.oauth.resultsdb.authorize_access_token()
userinfo = token["userinfo"]
user = userinfo[app.config["OIDC_USERNAME_FIELD"]]
_verify_authorization(user, body.testcase)

testcase = Testcase.query.filter_by(name=body.testcase).first()
Expand Down Expand Up @@ -68,7 +68,6 @@ def create_result(body: ResultParamsBase):
def create_endpoint(params_class):
params = params_class.construct()

@oidc.accept_token(require_token=True)
@validate()
def create(body: params_class):
return create_result(body)
Expand Down Expand Up @@ -134,6 +133,3 @@ def index():
endpoints=endpoints,
result_outcomes_extended=", ".join(result_outcomes_extended()),
)


create_endpoints()
10 changes: 9 additions & 1 deletion testing/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from unittest.mock import patch

import pytest

Expand All @@ -7,7 +8,14 @@


@pytest.fixture(scope="session", autouse=True)
def app():
def mock_oidc():
with patch("resultsdb.OAuth") as oauth:
oauth().resultsdb.authorize_access_token.return_value = {"userinfo": {"uid": "testuser1"}}
yield


@pytest.fixture(scope="session", autouse=True)
def app(mock_oidc):
app = create_app("resultsdb.config.TestingConfig")
with app.app_context():
db.drop_all()
Expand Down
24 changes: 9 additions & 15 deletions testing/test_api_v3.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# SPDX-License-Identifier: GPL-2.0+
from unittest.mock import ANY, patch, Mock

import flask
import pytest

from resultsdb.models import db
from resultsdb.parsers.api_v3 import RESULTS_PARAMS_CLASSES
from resultsdb.controllers.api_v3 import oidc


@pytest.fixture(scope="function", autouse=True)
def db_session():
db.session.rollback()
db.drop_all()
db.create_all()


@pytest.fixture(autouse=True)
Expand All @@ -17,18 +23,6 @@ def mock_ldap():
yield con


@pytest.fixture(autouse=True)
def mock_oidc():
with patch.object(oidc, "validate_token") as validate:

def validate_side_effect(*args, **kwargs):
flask.g.oidc_token_info = {"uid": "testuser1"}
return True

validate.side_effect = validate_side_effect
yield


@pytest.fixture
def client(app):
return app.test_client()
Expand Down Expand Up @@ -133,7 +127,7 @@ def test_api_v3_create_redhat_container_image(client):
assert r.json["testcase"] == {
"href": "http://localhost/api/v2.0/testcases/testcase1",
"name": "testcase1",
"ref_url": "https://test.example.com/docs/testcase1",
"ref_url": None,
}
assert r.json["data"]["item"] == [data["item"]]
assert r.json["data"]["type"] == ["redhat-container-image"]
Expand Down

0 comments on commit 248f93e

Please sign in to comment.