-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/add scope filter #14
base: main
Are you sure you want to change the base?
Changes from all commits
df8fcae
e316db6
79e3933
1512e58
fc099f9
3677e87
752c89e
e6dd0bd
9d5a155
3d201f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,39 @@ | |
import jwt | ||
import requests | ||
import json | ||
import re | ||
|
||
from jwt_proxy.audit import audit_HAPI_change | ||
|
||
blueprint = Blueprint('auth', __name__) | ||
SUPPORTED_METHODS = ('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS') | ||
|
||
# TODO: to be pulled into its own module and loaded per config | ||
def scope_filter(req, token): | ||
# Check path | ||
resource_pattern = rf"(Patient|DocumentReference)$" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fear this approach leaves a backdoor to well tailored requests. A user could include a string like "Patient" in say an or-clause of a search, or the like. Any reason to not just limit to the requested resource in the request path? i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's the intention of this pattern, to enforce the request path (sans params) ends in either |
||
if re.search(resource_pattern, req.path) is None: | ||
return False | ||
|
||
user_id = token.get("sub") | ||
identifier_pattern = rf"(https(:|%3[Aa])(\/|%2[Ff]){2}keycloak\.ltt\.cirg\.uw\.edu(%7[Cc]|\|))?{user_id}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is rather hardcoded to one keycloak install. it would be nice to look to a config value, similar to any reason to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks Paul, good points - This pattern just matches I wasn't aware of All that being said, I'll clean up this pattern - I'd hedged it a bit since I didn't know exactly what was url-encoded and what wasn't, but I've found that the query param values are always url-decoded by the |
||
|
||
# Search params for identifier-like param containing keycloak id | ||
params = req.args | ||
id_param_value = params.get("identifier", params.get("_identifier", params.get("subject.identifier"))) | ||
if id_param_value is not None and re.search(identifier_pattern, id_param_value): | ||
return True | ||
|
||
# Search body for subject.reference containing keycloak id | ||
body = req.get_json() # Propegates a 400 BadRequest on failure | ||
resource_type = body.get("resourceType") | ||
if resource_type == "DocumentReference": | ||
reference_string = body.get("subject", {}).get("reference") | ||
if reference_string is not None and re.search(identifier_pattern, reference_string): | ||
return True | ||
return False | ||
|
||
|
||
|
||
def proxy_request(req, upstream_url, user_info=None): | ||
"""Forward request to given url""" | ||
|
@@ -67,7 +94,12 @@ def validate_jwt(relative_path): | |
) | ||
except jwt.exceptions.ExpiredSignatureError: | ||
return jsonify(message="token expired"), 401 | ||
|
||
|
||
# TODO: call new function here to dynamically load a filter call dependent on config; hardwired for now | ||
scope_filter_ok = scope_filter(request, decoded_token) | ||
if not scope_filter_ok: | ||
return jsonify(message="Forbidden"), 403 | ||
|
||
response_content = proxy_request( | ||
req=request, | ||
upstream_url=f"{current_app.config['UPSTREAM_SERVER']}/{relative_path}", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i love the TODO. makes sense to me to move this to a separate module - by that i mean a separate .py file for easier migration later.