Skip to content
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

Enforcing Organization and Role API Permissions #127

Open
wants to merge 52 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
a37135c
Pulling org from token in api
jacob6838 Nov 15, 2024
0019096
Merging feature flags
jacob6838 Nov 15, 2024
65bc034
Adding admin endpoint org-based auth
jacob6838 Nov 16, 2024
454b130
Enforcing organization viewing/editing permissions
jacob6838 Nov 18, 2024
2641c00
updating imports
jacob6838 Nov 18, 2024
6e00ef0
Update auth_tools.py
jacob6838 Nov 18, 2024
429aab2
Working api permissions enforcement
jacob6838 Nov 19, 2024
84fd32c
adding _authorized to methods which enforce permissions
jacob6838 Nov 19, 2024
92e6b0b
Adding include_super_user to get_qualified_org_list
jacob6838 Nov 20, 2024
38d48b2
Adding exception handling for 400/403/500 errors
jacob6838 Nov 20, 2024
6b7fe15
Adding exceptions for admin_new_user
jacob6838 Nov 20, 2024
7e74215
Handling errors with exceptions
jacob6838 Nov 20, 2024
125d6ff
Merge branch 'api-exception-refactoring' into organization-permissions
jacob6838 Nov 20, 2024
336b44a
Converting unauth responses to exceptions
jacob6838 Nov 20, 2024
47cf1f5
Moving requirements to prevent warnings
jacob6838 Nov 20, 2024
04fcff8
denoting authorized methods
jacob6838 Nov 20, 2024
da0bf6e
Merge branch 'develop' into organization-permissions
jacob6838 Nov 26, 2024
cfb2c32
consolidating query_and_return_list, adding auth_tools tests
jacob6838 Nov 26, 2024
5593a41
Fixing admin email notifications tests
jacob6838 Nov 27, 2024
dedc1e7
Fixing authorized unit tests
jacob6838 Nov 27, 2024
52bdbba
Fixing remaining unit tests from auth additions
jacob6838 Nov 27, 2024
05ede0a
Formatting and type checking
jacob6838 Nov 27, 2024
fe32d7d
Fixing typos
jacob6838 Nov 27, 2024
88ed6fe
transitioning to decorators for auth checks
jacob6838 Nov 27, 2024
eb85031
Working auth decorator
jacob6838 Dec 2, 2024
ba9254a
Updating tests to use decorator
jacob6838 Dec 3, 2024
87b09b5
All tests working
jacob6838 Dec 3, 2024
f4759d0
bug fixes
jacob6838 Dec 4, 2024
9e19cfa
Merge branch 'feature-flags' into organization-permissions
jacob6838 Dec 4, 2024
eae7157
Merge branch 'feature-flags' into organization-permissions
jacob6838 Dec 4, 2024
188d44f
removing unused imports
jacob6838 Dec 5, 2024
5a9d106
Moving permissions to higher levels
jacob6838 Dec 5, 2024
56825d9
Tweaking tests and removing unused imports
jacob6838 Dec 5, 2024
b14950a
Cleaning up code, re-naming rs-geo-query
jacob6838 Dec 16, 2024
1c21fed
Merge branch 'develop' into organization-permissions
jacob6838 Dec 16, 2024
471c4d3
Merge branch 'feature-flags' into organization-permissions
jacob6838 Dec 18, 2024
80e1118
Merge branch 'pytest-warnings' into organization-permissions
jacob6838 Dec 18, 2024
73a2bb9
Using werkzeung exceptions, 401 -> 403, minor fixes
jacob6838 Dec 18, 2024
0f31619
Tweaking organizations object key
jacob6838 Dec 18, 2024
b5c0aa4
Adding @require_permission to every rest method
jacob6838 Dec 18, 2024
c02472f
cleaning up unused imports
jacob6838 Dec 18, 2024
824dbe4
re-working middleware auth enforcement
jacob6838 Dec 18, 2024
dae802f
Merge branch 'develop' into organization-permissions
jacob6838 Dec 20, 2024
6deedd0
Merge branch 'develop' into organization-permissions
jacob6838 Feb 10, 2025
7206bec
Fixing MyPy errors - sqlalchemy and auth_tools
jacob6838 Feb 11, 2025
c7b3d24
Cleaning up middleware
jacob6838 Feb 11, 2025
36fbf14
Merge branch 'develop' into organization-permissions
jacob6838 Feb 26, 2025
ed66aa1
Fixing mypy errors
jacob6838 Feb 26, 2025
9aef7f0
Fixing unit tests
jacob6838 Feb 26, 2025
4601236
Enforcing org-based auth filtering in RsuSsmSrmData
jacob6838 Feb 26, 2025
50e5350
Converting SQL exception catchers to SQLAlchemyError
jacob6838 Feb 26, 2025
875a440
Merge branch 'develop' into organization-permissions
jacob6838 Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,27 @@
},
"cSpell.words": [
"bbox",
"bgscheduler",
"BOOTSTRAPSERVERS",
"Bsms",
"cdot",
"cimms",
"cloudsql",
"collstats",
"commsignia",
"conflictmonitor",
"conflictvisualizer",
"creds",
"customdb",
"cviz",
"cvmanager",
"CVPEP",
"dateutil",
"dont",
"drivername",
"formik",
"fromaddr",
"Fwding",
"geojsonconverter",
"hamcrest",
"healthcheck",
Expand All @@ -46,33 +54,58 @@
"INITDB",
"inprog",
"JDBC",
"jsonify",
"Kapsch",
"keyfile",
"ksession",
"LOGLEVEL",
"luxon",
"mailhost",
"MESSAGETYPE",
"millis",
"mongosh",
"msgfwd",
"multidict",
"Multivalued",
"NTCIP",
"OIDC",
"OIDCID",
"pgdb",
"PGSQL",
"postgis",
"pythjon",
"pytz",
"querycounts",
"querydb",
"querymsgfwd",
"reduxjs",
"rsudsrcfwd",
"rsufwdsnmpset",
"rsufwdsnmpwalk",
"rsuinfo",
"rsus",
"rwdata",
"rxtxfwd",
"SASL",
"secretmanager",
"SNMP",
"snmpfilter",
"Snmpset",
"Snmpwalk",
"sqlalchemy",
"stfq",
"subquery",
"svcs",
"TABLENAME",
"toaddrs",
"upgrader",
"usdotjpoode",
"userauth",
"utctimestamp",
"writedb",
"wydot",
"Xmit",
"yunex",
"Zabbix"
],
"java.configuration.updateBuildConfiguration": "automatic"
Expand Down
22 changes: 21 additions & 1 deletion resources/sql_scripts/CVManager_CreateTables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,24 @@ CREATE TABLE IF NOT EXISTS public.max_retry_limit_reached_instances
REFERENCES public.firmware_images (firmware_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
);



-- Indexes
CREATE INDEX idx_organizations_name ON public.organizations (name);

-- RSUs
CREATE INDEX idx_rsu_organization ON public.rsu_organization (organization_id, rsu_id);
CREATE INDEX idx_rsus_ipv4_address ON public.rsus (ipv4_address);
CREATE INDEX idx_rsus_ipv4_rsu_id ON public.rsus (ipv4_address, rsu_id);

-- Intersections
CREATE INDEX idx_intersections_intersection_number ON public.intersections (intersection_number);
CREATE INDEX idx_intersection_id ON public.intersections (intersection_id);
CREATE INDEX idx_intersection_organization ON public.intersection_organization (organization_id, intersection_id);

-- Users
CREATE INDEX idx_users_email ON public.users (email);
CREATE INDEX idx_users_user_id ON public.users (user_id);
CREATE INDEX idx_user_organization ON public.user_organization (user_id, organization_id);
4 changes: 2 additions & 2 deletions sample_no_cm.env
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ STALE_PERIOD=24

# OBU OTA Server Addon:

# Routeable host name for the server
# Routable host name for the server
OBU_OTA_SERVER_HOST = "localhost"

# For users using GCP cloud storage
Expand All @@ -211,7 +211,7 @@ NGINX_ENCRYPTION="plain"
SERVER_CERT_FILE="ota_server.crt"
SERVER_KEY_FILE="ota_server.key"

# Max number of succesfull firmware upgrades to keep in the database per device SN
# Max number of successful firmware upgrades to keep in the database per device SN
MAX_COUNT = 10

# ---------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions services/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
site-packages
Scripts
pyvenv.cfg
.idea
Empty file added services/addons/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from werkzeug.exceptions import BadRequest
import addons.tests.firmware_manager.upgrade_runner.test_upgrade_runner_values as fmv

# start_upgrade_task tests


# start_upgrade_task tests
@patch("addons.images.firmware_manager.upgrade_runner.upgrade_runner.Popen")
def test_start_upgrade_task_success(mock_popen):
with upgrade_runner.app.app_context():
Expand Down Expand Up @@ -51,8 +50,6 @@ def test_start_upgrade_task_fail(mock_popen):


# run_firmware_upgrade tests


@patch(
"addons.images.firmware_manager.upgrade_runner.upgrade_runner.start_upgrade_task",
MagicMock(),
Expand Down Expand Up @@ -98,8 +95,6 @@ def test_run_firmware_upgrade_success(mock_logging, mock_start_upgrade_task):


# Other tests


@patch("addons.images.firmware_manager.upgrade_runner.upgrade_runner.serve")
def test_serve_rest_api(mock_serve):
upgrade_runner.serve_rest_api()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ def test_get_rsu_upgrade_data_one(mock_querydb):


# start_tasks_from_queue tests


@patch.dict("os.environ", {"UPGRADE_RUNNER_ENDPOINT": "http://test-endpoint"})
@patch(
"addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.active_upgrades",
Expand Down Expand Up @@ -246,8 +244,6 @@ def test_start_tasks_from_queue_post_fail(mock_post, mock_logging):


# init_firmware_upgrade tests


@patch(
"addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.active_upgrades",
{},
Expand Down Expand Up @@ -464,8 +460,6 @@ def test_init_firmware_upgrade_success(


# firmware_upgrade_completed tests


@patch("addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.logging")
@patch(
"addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.active_upgrades",
Expand Down Expand Up @@ -809,8 +803,6 @@ def test_firmware_upgrade_completed_success_status_exception(


# list_active_upgrades tests


@patch("addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.logging")
@patch(
"addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.active_upgrades",
Expand Down Expand Up @@ -853,8 +845,6 @@ def test_list_active_upgrades(mock_logging):


# check_for_upgrades tests


@patch.dict("os.environ", {"UPGRADE_RUNNER_ENDPOINT": "http://test-endpoint"})
@patch(
"addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.was_latest_ping_successful_for_rsu"
Expand Down Expand Up @@ -953,8 +943,6 @@ def test_check_for_upgrades(


# Other tests


@patch(
"addons.images.firmware_manager.upgrade_scheduler.upgrade_scheduler.pgquery.query_db"
)
Expand Down
7 changes: 1 addition & 6 deletions services/addons/tests/iss_health_check/test_iss_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@

from addons.images.iss_health_check import iss_token

# --------------------- Storage Type tests ---------------------


# --------------------- Storage Type tests ---------------------
@patch.dict(
os.environ,
{
Expand Down Expand Up @@ -71,8 +70,6 @@ def test_get_storage_type_unset():


# --------------------- end of Storage Type tests ---------------------


# --------------------- GCP tests ---------------------
@patch(
"addons.images.iss_health_check.iss_token.secretmanager.SecretManagerServiceClient"
Expand Down Expand Up @@ -305,8 +302,6 @@ def test_get_token_secret_exists(


# --------------------- Postgres tests ---------------------


@patch(
"addons.images.iss_health_check.iss_token.pgquery",
)
Expand Down
26 changes: 19 additions & 7 deletions services/addons/tests/obu_ota_server/test_obu_ota_server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
import tempfile
import os
from httpx import AsyncClient, BasicAuth
from httpx import ASGITransport, AsyncClient, BasicAuth
from fastapi import HTTPException, Request
from unittest.mock import patch, MagicMock
from datetime import datetime
Expand Down Expand Up @@ -201,7 +201,9 @@ async def test_read_file_no_end_range():
@patch.dict("os.environ", {"OTA_USERNAME": "username", "OTA_PASSWORD": "password"})
@pytest.mark.anyio
async def test_read_root():
async with AsyncClient(app=app, base_url="http://test") as ac:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "obu ota server healthcheck", "root_path": ""}
Expand All @@ -217,7 +219,9 @@ async def test_get_manifest(mock_commsignia_manifest, mock_get_firmware_list):
"/firmwares/test2.tar.sig",
]
mock_commsignia_manifest.return_value = {"json": "data"}
async with AsyncClient(app=app, base_url="http://test") as ac:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get(
"/firmwares/commsignia", auth=BasicAuth("username", "password")
)
Expand All @@ -234,7 +238,9 @@ async def test_get_fw(mock_read_file, mock_parse_range_header, mock_get_firmware
mock_get_firmware.return_value = True
mock_parse_range_header.return_value = 0, 100
mock_read_file.return_value = b"Test data", 100, 100
async with AsyncClient(app=app, base_url="http://test") as ac:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get(
"/firmwares/commsignia/test_firmware_id",
auth=BasicAuth("username", "password"),
Expand Down Expand Up @@ -334,7 +340,9 @@ async def test_get_manifest(mock_commsignia_manifest, mock_get_firmware_list):
"/firmwares/test2.tar.sig",
]
mock_commsignia_manifest.return_value = {"json": "data"}
async with AsyncClient(app=app, base_url="http://test") as ac:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get(
"/firmwares/commsignia", auth=BasicAuth("username", "password")
)
Expand All @@ -358,7 +366,9 @@ async def test_fqdn_response_plain(mock_commsignia_manifest, mock_get_firmware_l
expected_hostname = "http://localhost"
mock_commsignia_manifest.return_value = {"json": "data"}

async with AsyncClient(app=app, base_url="http://test") as ac:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get(
"/firmwares/commsignia", auth=BasicAuth("username", "password")
)
Expand All @@ -383,7 +393,9 @@ async def test_fqdn_response_ssl(mock_commsignia_manifest, mock_get_firmware_lis
expected_hostname = "https://localhost"
mock_commsignia_manifest.return_value = {"json": "data"}

async with AsyncClient(app=app, base_url="http://test") as ac:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get(
"/firmwares/commsignia", auth=BasicAuth("username", "password")
)
Expand Down
3 changes: 1 addition & 2 deletions services/api/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ requests==2.31.0
sqlalchemy==2.0.21
pg8000==1.30.2
DateTime==5.2
google-cloud-bigquery==3.14.1
python-dateutil==2.8.2
pytz==2023.3.post1
Werkzeug==3.0.0
python-keycloak==2.16.2
python-keycloak==3.12.0
pymongo==4.5.0
fabric==3.2.2
Loading
Loading