Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions api/routes/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@
from core.DTOs.FinalReviewApprovalInfo import FinalReviewApprovalInfo, FinalReviewBaseInfo
from core.DTOs.GetNextURLForFinalReviewResponse import GetNextURLForFinalReviewResponse, \
GetNextURLForFinalReviewOuterResponse
from security_manager.SecurityManager import AccessInfo, get_access_info
from security_manager.SecurityManager import AccessInfo, get_access_info, require_permission, Permissions

Check warning on line 10 in api/routes/review.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] api/routes/review.py#L10 <401>

'security_manager.SecurityManager.get_access_info' imported but unused
Raw output
./api/routes/review.py:10:1: F401 'security_manager.SecurityManager.get_access_info' imported but unused

review_router = APIRouter(
prefix="/review",
tags=["Review"],
responses={404: {"description": "Not found"}},
)

requires_final_review_permission = require_permission(Permissions.SOURCE_COLLECTOR_FINAL_REVIEW)

@review_router.get("/next-source")
async def get_next_source(
core: AsyncCore = Depends(get_async_core),
access_info: AccessInfo = Depends(get_access_info),
access_info: AccessInfo = Depends(requires_final_review_permission),

Check warning on line 23 in api/routes/review.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] api/routes/review.py#L23 <100>

Unused argument 'access_info'
Raw output
./api/routes/review.py:23:5: U100 Unused argument 'access_info'
batch_id: Optional[int] = Query(
description="The batch id of the next URL to get. "
"If not specified, defaults to first qualifying URL",
Expand All @@ -30,7 +32,7 @@
@review_router.post("/approve-source")
async def approve_source(
core: AsyncCore = Depends(get_async_core),
access_info: AccessInfo = Depends(get_access_info),
access_info: AccessInfo = Depends(requires_final_review_permission),
approval_info: FinalReviewApprovalInfo = FinalReviewApprovalInfo,
batch_id: Optional[int] = Query(
description="The batch id of the next URL to get. "
Expand All @@ -47,7 +49,7 @@
@review_router.post("/reject-source")
async def reject_source(
core: AsyncCore = Depends(get_async_core),
access_info: AccessInfo = Depends(get_access_info),
access_info: AccessInfo = Depends(requires_final_review_permission),
review_info: FinalReviewBaseInfo = FinalReviewBaseInfo,
batch_id: Optional[int] = Query(
description="The batch id of the next URL to get. "
Expand Down
16 changes: 13 additions & 3 deletions security_manager/SecurityManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

class Permissions(Enum):
SOURCE_COLLECTOR = "source_collector"
SOURCE_COLLECTOR_FINAL_REVIEW = "source_collector_final_review"

class AccessInfo(BaseModel):
user_id: int
Expand Down Expand Up @@ -64,9 +65,13 @@
continue
return relevant_permissions

def check_access(self, token: str) -> AccessInfo:
def check_access(

Check warning on line 68 in security_manager/SecurityManager.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] security_manager/SecurityManager.py#L68 <102>

Missing docstring in public method
Raw output
./security_manager/SecurityManager.py:68:1: D102 Missing docstring in public method
self,
token: str,
permission: Permissions
) -> AccessInfo:
access_info = self.validate_token(token)
if not access_info.has_permission(Permissions.SOURCE_COLLECTOR):
if not access_info.has_permission(permission):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access forbidden",
Expand All @@ -79,4 +84,9 @@
def get_access_info(
token: Annotated[str, Depends(oauth2_scheme)]
) -> AccessInfo:
return SecurityManager().check_access(token)
return SecurityManager().check_access(token, Permissions.SOURCE_COLLECTOR)

def require_permission(permission: Permissions):

Check warning on line 89 in security_manager/SecurityManager.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] security_manager/SecurityManager.py#L89 <103>

Missing docstring in public function
Raw output
./security_manager/SecurityManager.py:89:1: D103 Missing docstring in public function
def dependency(token: Annotated[str, Depends(oauth2_scheme)]) -> AccessInfo:
return SecurityManager().check_access(token, permission=permission)
return dependency

Check warning on line 92 in security_manager/SecurityManager.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] security_manager/SecurityManager.py#L92 <292>

no newline at end of file
Raw output
./security_manager/SecurityManager.py:92:22: W292 no newline at end of file
12 changes: 10 additions & 2 deletions tests/test_automated/integration/api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

from api.main import app
from core.AsyncCore import AsyncCore
from api.routes.review import requires_final_review_permission
from core.SourceCollectorCore import SourceCollectorCore
from security_manager.SecurityManager import get_access_info, AccessInfo, Permissions
from security_manager.SecurityManager import get_access_info, AccessInfo, Permissions, require_permission

Check warning on line 13 in tests/test_automated/integration/api/conftest.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] tests/test_automated/integration/api/conftest.py#L13 <401>

'security_manager.SecurityManager.require_permission' imported but unused
Raw output
./tests/test_automated/integration/api/conftest.py:13:1: F401 'security_manager.SecurityManager.require_permission' imported but unused
from tests.helpers.DBDataCreator import DBDataCreator
from tests.test_automated.integration.api.helpers.RequestValidator import RequestValidator

Expand Down Expand Up @@ -39,13 +40,20 @@
)

def override_access_info() -> AccessInfo:
return AccessInfo(user_id=MOCK_USER_ID, permissions=[Permissions.SOURCE_COLLECTOR])
return AccessInfo(
user_id=MOCK_USER_ID,
permissions=[
Permissions.SOURCE_COLLECTOR,
Permissions.SOURCE_COLLECTOR_FINAL_REVIEW
]
)

@pytest.fixture(scope="session")
def client() -> Generator[TestClient, None, None]:
# Mock environment
with TestClient(app) as c:
app.dependency_overrides[get_access_info] = override_access_info
app.dependency_overrides[requires_final_review_permission] = override_access_info
async_core: AsyncCore = c.app.state.async_core

# Interfaces to the web should be mocked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ def test_validate_token_failure(mock_get_secret_key, mock_jwt_decode):

def test_check_access_success(mock_get_secret_key, mock_jwt_decode):
sm = SecurityManager()
sm.check_access(VALID_TOKEN) # Should not raise any exceptions.
sm.check_access(VALID_TOKEN, Permissions.SOURCE_COLLECTOR) # Should not raise any exceptions.


def test_check_access_failure(mock_get_secret_key, mock_jwt_decode):
# Modify payload to have insufficient permissions
with patch(get_patch_path("SecurityManager.validate_token"), return_value=AccessInfo(user_id=1, permissions=[])):
sm = SecurityManager()
with pytest.raises(HTTPException) as exc_info:
sm.check_access(VALID_TOKEN)
sm.check_access(VALID_TOKEN, Permissions.SOURCE_COLLECTOR)
assert exc_info.value.status_code == 403


Expand Down