Skip to content

Commit

Permalink
Merge pull request #39 from PolicyEngine/feat/add_auth
Browse files Browse the repository at this point in the history
Add user authentication
  • Loading branch information
anth-volk authored Jan 3, 2024
2 parents 6184e22 + 1cd2819 commit 413cb7a
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ jobs:
run: make install
- name: Test the API
run: make test
env:
AUTH0_ADDRESS_NO_DOMAIN: ${{ secrets.AUTH0_ADDRESS_NO_DOMAIN }}
AUTH0_AUDIENCE_NO_DOMAIN: ${{ secrets.AUTH0_AUDIENCE_NO_DOMAIN }}
AUTH0_TEST_TOKEN_NO_DOMAIN: ${{ secrets.AUTH0_TEST_TOKEN_NO_DOMAIN }}
3 changes: 3 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ jobs:
run: make deploy
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GCP_SA_KEY }}
AUTH0_ADDRESS_NO_DOMAIN: ${{ secrets.AUTH0_ADDRESS_NO_DOMAIN }}
AUTH0_AUDIENCE_NO_DOMAIN: ${{ secrets.AUTH0_AUDIENCE_NO_DOMAIN }}
AUTH0_TEST_TOKEN_NO_DOMAIN: ${{ secrets.AUTH0_TEST_TOKEN_NO_DOMAIN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ dist/*
**/*.rdb
**/*.h5
**/*.csv.gz
.env
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- Added authentication via auth0
20 changes: 18 additions & 2 deletions gcp/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@
from pathlib import Path

GAE = os.environ["GOOGLE_APPLICATION_CREDENTIALS"]
# If it's a filepath, read the file. Otherwise, it'll be JSON
# If it's a filepath, read the file. Otherwise, it'll be text
try:
Path(GAE).resolve(strict=True)
with open(GAE, "r") as f:
GAE = f.read()
except Exception as e:
pass
ADDRESS = os.environ["AUTH0_ADDRESS_NO_DOMAIN"]
AUDIENCE = os.environ["AUTH0_AUDIENCE_NO_DOMAIN"]
TEST_TOKEN = os.environ["AUTH0_TEST_TOKEN_NO_DOMAIN"]

# Export GAE to to .gac.json and DB_PD to .dbpw in the current directory
# Export GAE to to .gac.json

with open(".gac.json", "w") as f:
f.write(GAE)

# in gcp/policyengine_api_light/Dockerfile, replace env variables
for dockerfile_location in [
"gcp/policyengine_api_light/Dockerfile",
]:
with open(dockerfile_location, "r") as f:
dockerfile = f.read()
dockerfile = dockerfile.replace(".address", ADDRESS)
dockerfile = dockerfile.replace(".audience", AUDIENCE)
dockerfile = dockerfile.replace(".test-token", TEST_TOKEN)

with open(dockerfile_location, "w") as f:
f.write(dockerfile)
3 changes: 3 additions & 0 deletions gcp/policyengine_api_light/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
FROM anthvolk/policyengine-api-light:latest
ENV GOOGLE_APPLICATION_CREDENTIALS .gac.json
ENV AUTH0_ADDRESS_NO_DOMAIN .address
ENV AUTH0_AUDIENCE_NO_DOMAIN .audience
ENV AUTH0_TEST_TOKEN_NO_DOMAIN .test-token
ADD . /app
RUN cd /app && make install && make test
CMD ./start.sh
18 changes: 18 additions & 0 deletions policyengine_api_light/auth/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json
from urllib.request import urlopen

from authlib.oauth2.rfc7523 import JWTBearerTokenValidator
from authlib.jose.rfc7517.jwk import JsonWebKey


class Auth0JWTBearerTokenValidator(JWTBearerTokenValidator):
def __init__(self, domain, audience):
issuer = f"https://{domain}/"
jsonurl = urlopen(f"{issuer}.well-known/jwks.json")
public_key = JsonWebKey.import_key_set(json.loads(jsonurl.read()))
super(Auth0JWTBearerTokenValidator, self).__init__(public_key)
self.claims_options = {
"exp": {"essential": True},
"aud": {"essential": True, "value": audience},
"iss": {"essential": True, "value": issuer},
}
14 changes: 14 additions & 0 deletions policyengine_api_light/endpoints/household.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@
COUNTRIES,
validate_country,
)
import os
import json
from flask import Response, request
from policyengine_api_light.country import COUNTRIES
import json
import logging
from dotenv import load_dotenv
from authlib.integrations.flask_oauth2 import ResourceProtector
from ..auth.validation import Auth0JWTBearerTokenValidator

load_dotenv()

# Configure authentication
require_auth = ResourceProtector()
validator = Auth0JWTBearerTokenValidator(
os.getenv("AUTH0_ADDRESS_NO_DOMAIN"), os.getenv("AUTH0_AUDIENCE_NO_DOMAIN")
)
require_auth.register_token_validator(validator)


def add_yearly_variables(household, country_id):
Expand Down Expand Up @@ -43,6 +56,7 @@ def add_yearly_variables(household, country_id):
return household


@require_auth(None)
def get_calculate(country_id: str, add_missing: bool = False) -> dict:
"""Lightweight endpoint for passing in household and policy JSON objects and calculating without storing data.
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
description="PolicyEngine API Light",
packages=find_packages(),
install_requires=[
"Authlib<1.3.0",
"flask>=1",
"flask-cors>=3",
"google-cloud-logging",
Expand All @@ -21,6 +22,7 @@
"policyengine_us==0.571.2",
"Flask-Caching==2.0.2",
"urllib3<1.27,>=1.21.1",
"python-dotenv",
],
extras_require={
"dev": [
Expand Down
13 changes: 11 additions & 2 deletions tests/python/test_liveness.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import os
from dotenv import load_dotenv

from tests.python.utils import client

load_dotenv()


def test_calculate_liveness(client):
"""This tests that, when passed relevant data, calculate endpoint functions properly"""
"""This tests that, when passed relevant data, calculate endpoint returns something"""
response = client.post(
"/us/calculate",
headers={"Content-Type": "application/json"},
headers={
"Content-Type": "application/json",
"Authorization": "Bearer "
+ os.getenv("AUTH0_TEST_TOKEN_NO_DOMAIN"),
},
data=open(
"./tests/python/data/calculate_us_1_data.json",
"r",
Expand Down
10 changes: 9 additions & 1 deletion tests/python/test_sync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import os
import requests
import json
import sys
from dotenv import load_dotenv

load_dotenv()

from tests.python.utils import client, extract_json_from_file

Expand All @@ -25,7 +29,11 @@ def test_calculate_sync(client):
# Mock a POST request to API-light
resLight = client.post(
"/" + country_id + "/calculate",
headers={"Content-Type": "application/json"},
headers={
"Content-Type": "application/json",
"Authorization": "Bearer "
+ os.getenv("AUTH0_TEST_TOKEN_NO_DOMAIN"),
},
json=input_data,
).get_json()

Expand Down

0 comments on commit 413cb7a

Please sign in to comment.