Skip to content

Commit

Permalink
Merge pull request #68 from medianetlab/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
JFrgs authored Feb 24, 2023
2 parents c554659 + eaccf0f commit 696bcb6
Show file tree
Hide file tree
Showing 22 changed files with 284 additions and 49 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
.env
*.key
*.crt
!ca.crt
capif_exposer_details.json
*.csr
*.pem
backend/app/app/core/certificates/capif_provider_details.json
venv/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ build:
docker-compose --profile debug build

build-no-cache:
docker-compose --profile debug build --no-cache
docker-compose --profile debug build --no-cache --pull

logs:
docker-compose logs -f
Expand Down
11 changes: 10 additions & 1 deletion backend/Dockerfile.backend
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.10

WORKDIR /app/

Expand All @@ -24,4 +24,13 @@ RUN bash -c "if [ $INSTALL_JUPYTER == 'true' ] ; then pip install jupyterlab ; f
COPY ./app /app
ENV PYTHONPATH=/app

WORKDIR /

RUN git clone https://github.com/EVOLVED-5G/SDK-CLI.git && \
cd SDK-CLI/ && \
git checkout libraries && \
python3 setup.py install && \
cp -R evolved5g /usr/local/lib/python3.10/site-packages/

WORKDIR /app/
COPY ./start-reload.sh /
8 changes: 6 additions & 2 deletions backend/app/app/api/api_v1/endpoints/monitoringevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session
from pymongo.database import Database
from app import models, schemas
from app.crud import crud_mongo, user, ue
from app.api import deps
Expand All @@ -15,11 +14,12 @@
router = APIRouter()
db_collection= 'MonitoringEvent'

@router.get("/{scsAsId}/subscriptions", response_model=List[schemas.MonitoringEventSubscription], responses={204: {"model" : None}})
@router.get("/{scsAsId}/subscriptions")
def read_active_subscriptions(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that read all the subscriptions", example="myNetapp"),
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -62,6 +62,7 @@ def create_subscription(
db: Session = Depends(deps.get_db),
item_in: schemas.MonitoringEventSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -171,6 +172,7 @@ def update_subscription(
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
item_in: schemas.MonitoringEventSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -215,6 +217,7 @@ def read_subscription(
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -252,6 +255,7 @@ def delete_subscription(
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down
5 changes: 5 additions & 0 deletions backend/app/app/api/api_v1/endpoints/qosMonitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def read_active_subscriptions(
*,
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -51,6 +52,7 @@ def create_subscription(
db: Session = Depends(deps.get_db),
item_in: schemas.AsSessionWithQoSSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:

Expand Down Expand Up @@ -139,6 +141,7 @@ def read_subscription(
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -170,6 +173,7 @@ def update_subscription(
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
item_in: schemas.AsSessionWithQoSSubscriptionCreate,
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down Expand Up @@ -206,6 +210,7 @@ def delete_subscription(
scsAsId: str = Path(..., title="The ID of the Netapp that creates a subscription", example="myNetapp"),
subscriptionId: str = Path(..., title="Identifier of the subscription resource"),
current_user: models.User = Depends(deps.get_current_active_user),
token_payload = Depends(deps.verify_with_public_key),
http_request: Request
) -> Any:
"""
Expand Down
44 changes: 37 additions & 7 deletions backend/app/app/api/deps.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from typing import Generator
from typing import Generator, Dict
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
from pydantic import ValidationError
from sqlalchemy.orm import Session

from app import crud, models, schemas
from app.core import security
from app.core.config import settings
from app.db.session import SessionLocal
from app.core.config import settings

reusable_oauth2 = OAuth2PasswordBearer(
reusable_oauth2 = security.OAuth2TwoTokensBearer(
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
)

Expand All @@ -22,18 +21,49 @@ def get_db() -> Generator:
finally:
db.close() #The code following the yield statement is executed after the response has been delivered

def verify_with_public_key(tokens: Dict[str, str] = Depends(reusable_oauth2)):
'''
Enable verification of a token generated by CAPIF for an API Invoker.
If env variable USE_PUBLIC_KEY_VERIFICATION is set to False, the verification is ommited.
'''
if settings.USE_PUBLIC_KEY_VERIFICATION == True:
try:
if "capif_token" in tokens:
token = tokens.get("capif_token")
print(f"CAPIF token {token}")
else:
token = tokens.get("token")

payload = jwt.decode(
token, security.extract_public_key('/app/app/core/certificates/capif_cert_server.pem'), algorithms=[security.ALGORITHM[1]]
)
except (jwt.JWTError):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials - Token from CAPIF",
)
return payload
else:
return None

def get_current_user(
db: Session = Depends(get_db), token: str = Depends(reusable_oauth2)
db: Session = Depends(get_db), tokens: Dict[str, str] = Depends(reusable_oauth2)
) -> models.User:
try:
if "nef_token" in tokens:
token = tokens.get("nef_token")
print(f"NEF token {token}")
else:
token = tokens.get("token")

payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM[0]]
)
token_data = schemas.TokenPayload(**payload)
except (jwt.JWTError, ValidationError):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
detail="Could not validate credentials - Token from NEF",
)
user = crud.user.get(db, id=token_data.sub)
if not user:
Expand Down
18 changes: 13 additions & 5 deletions backend/app/app/backend_pre_start.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging, requests
from evolved5g.sdk import CAPIFExposerConnector
from evolved5g.sdk import CAPIFProviderConnector
from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed
from app.db.session import SessionLocal
from app.core.config import settings
Expand Down Expand Up @@ -31,16 +31,24 @@ def capif_nef_connector():
"""
try:
capif_connector = CAPIFExposerConnector(certificates_folder="app/core/certificates",
capif_connector = CAPIFProviderConnector(certificates_folder="app/core/certificates",
capif_host=settings.CAPIF_HOST,
capif_http_port=settings.CAPIF_HTTP_PORT,
capif_https_port=settings.CAPIF_HTTPS_PORT,
capif_netapp_username="test_nef01",
capif_netapp_password="test_netapp_password",
description= "test_app_description"
)
description= "test_app_description",
csr_common_name="apfExpapfoser1502", #TODO: ASK STAVROS. THIS SHOULD NOT BE HARDCODED, RIGHT?
csr_organizational_unit="test_app_ou",
csr_organization="test_app_o",
crs_locality="Madrid",
csr_state_or_province_name="Madrid",
csr_country_name="ES",
csr_email_address="[email protected]"
)


capif_connector.register_and_onboard_exposer()
capif_connector.register_and_onboard_provider()

capif_connector.publish_services(service_api_description_json_full_path="app/core/capif_files/service_monitoring_event.json")
capif_connector.publish_services(service_api_description_json_full_path="app/core/capif_files/service_as_session_with_qos.json")
Expand Down
Empty file.
20 changes: 0 additions & 20 deletions backend/app/app/core/certificates/ca.crt

This file was deleted.

2 changes: 2 additions & 0 deletions backend/app/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ def get_emails_enabled(cls, v: bool, values: Dict[str, Any]) -> bool:
FIRST_SUPERUSER_PASSWORD: str
USERS_OPEN_REGISTRATION: bool = False

USE_PUBLIC_KEY_VERIFICATION: bool

class Config:
case_sensitive = True

Expand Down
69 changes: 65 additions & 4 deletions backend/app/app/core/security.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
from datetime import datetime, timedelta
from typing import Any, Union

from OpenSSL import crypto
from jose import jwt
from passlib.context import CryptContext

from typing import Optional, Dict, Tuple
from fastapi import HTTPException, Request, status
from fastapi.security import OAuth2
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
from fastapi.security.utils import get_authorization_scheme_param
from app.core.config import settings

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


ALGORITHM = "HS256"
ALGORITHM = ("HS256", "RS256")

class OAuth2TwoTokensBearer(OAuth2):
'''
Override OAuth2 class based on FastAPI's OAuth2PasswordBearer to support two tokens bearer to authorise either NEF or CAPIF jtw tokens
This implementation takes the Authorization header and splits the token parameter into two tokens, assuming they are separated by a comma. It returns a tuple containing the two tokens.
'''
def __init__(
self,
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
super().__init__(
flows=flows,
scheme_name=scheme_name,
description=description,
auto_error=auto_error,
)

async def __call__(self, request: Request) -> Optional[Tuple[str, str]]:
authorization: str = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None

try:
nef_token, capif_token = param.split(',')
print("Parameter splitted")
except ValueError as ex:
return {"token" : param}

return {"nef_token" : nef_token, "capif_token" : capif_token}


def create_access_token(
subject: Union[str, Any], expires_delta: timedelta = None
Expand All @@ -22,7 +71,7 @@ def create_access_token(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode = {"exp": expire, "sub": str(subject)}
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM[0])
return encoded_jwt


Expand All @@ -32,3 +81,15 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:

def get_password_hash(password: str) -> str:
return pwd_context.hash(password)

def extract_public_key(cert_path: str):
try:
with open(cert_path, 'r') as f:
cert = f.read()
except FileNotFoundError as e:
print(e)

crtObj = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
pubKeyObject = crtObj.get_pubkey()
pubKeyString = crypto.dump_publickey(crypto.FILETYPE_PEM,pubKeyObject)
return pubKeyString
2 changes: 1 addition & 1 deletion backend/app/prestart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ python /app/app/backend_pre_start.py
#alembic upgrade head

# Create initial data in DB
python /app/app/initial_data.py
python /app/app/initial_data.py
Loading

0 comments on commit 696bcb6

Please sign in to comment.