Skip to content

Commit

Permalink
..
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiog1901 committed Jan 18, 2024
1 parent 3953fe4 commit ed382b1
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 573 deletions.
12 changes: 6 additions & 6 deletions apiserver/api_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async def get_parent_chain(

@self.get(
"/{id}/attachment-list",
dependencies=[Security(dep.get_current_user, scopes=["rw"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_write"])],
)
async def get_attachment_list(
id: UUID,
Expand All @@ -83,7 +83,7 @@ async def get_all_children_for_model(
async def create_instance(
model: update_model,
current_user: Annotated[
User, Security(dep.get_current_user, scopes=["rw"])
User, Security(dep.get_current_user, scopes=["worst_write"])
],
bg_task: BackgroundTasks,
) -> default_model | None:
Expand All @@ -107,7 +107,7 @@ async def create_instance(
async def update_instance(
model: update_model,
current_user: Annotated[
User, Security(dep.get_current_user, scopes=["rw"])
User, Security(dep.get_current_user, scopes=["worst_write"])
],
bg_task: BackgroundTasks,
) -> default_model | None:
Expand All @@ -131,7 +131,7 @@ async def update_instance(
async def delete_instance(
id: UUID,
current_user: Annotated[
User, Security(dep.get_current_user, scopes=["rw"])
User, Security(dep.get_current_user, scopes=["worst_write"])
],
bg_task: BackgroundTasks,
) -> default_model | None:
Expand Down Expand Up @@ -165,7 +165,7 @@ async def get_presigned_get_url(

@self.get(
"/{id}/presigned-put-url/{filename}",
dependencies=[Security(dep.get_current_user, scopes=["rw"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_write"])],
name="Get pre-signed URL for uploading an attachment",
)
async def get_presigned_put_url(
Expand All @@ -179,7 +179,7 @@ async def get_presigned_put_url(

@self.delete(
"/{id}/attachments/{filename}",
dependencies=[Security(dep.get_current_user, scopes=["rw"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_write"])],
)
async def delete_attachement(
id: UUID,
Expand Down
97 changes: 70 additions & 27 deletions apiserver/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from apiserver.models import UserInDB
from fastapi import Depends, HTTPException, status, BackgroundTasks, APIRouter
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
from jose import jwt
import jwt
from jwt.algorithms import RSAAlgorithm
from minio.deleteobjects import DeleteObject
from passlib.context import CryptContext
Expand All @@ -16,14 +16,23 @@

JWKS = os.getenv("JWKS")
ALGORITHM = os.getenv("ALGORITHM")
CLIENT_ID = os.getenv("CLIENT_ID")
ISSUER = os.getenv("ISSUER")
USERNAME_CLAIM = os.getenv("USERNAME_CLAIM")

# to get a string like this run:
# openssl rand -hex 32
JWT_KEY = os.getenv("JWT_KEY")
JWT_KEY_ALGORITHM = os.getenv("JWT_KEY_ALGORITHM")

if not JWKS or not ALGORITHM:
raise EnvironmentError("JWKS or ALGORITHM env variables not found!")

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

oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="login", scopes={"rw": "rw", "admin": "admin"}
tokenUrl="login",
scopes={"worst_read": "read", "worst_write": "write", "worst_admin": "admin"},
)


Expand Down Expand Up @@ -102,10 +111,7 @@ def s3_delete_all_objects(folder: str):
pass


def get_current_user(
security_scopes: SecurityScopes,
token: str = Depends(oauth2_scheme),
):
def decode_token(token: str):
unverified_header = jwt.get_unverified_header(token)

rsa_key = {}
Expand All @@ -120,45 +126,82 @@ def get_current_user(
}

if rsa_key:
print("rsa")
try:
public_key = RSAAlgorithm.from_jwk(rsa_key)
payload = jwt.decode(
token,
public_key,
algorithms=ALGORITHM,
options=dict(
verify_aud=False,
verify_sub=False,
verify_exp=False,
),
algorithms=[
unverified_header["alg"],
],
audience=CLIENT_ID,
issuer=ISSUER
# options=dict(
# verify_aud=False,
# verify_sub=False,
# verify_exp=True,
# ),
)

except jwt.ExpiredSignatureError:
print("129")
raise HTTPException(
{"code": "token_expired", "description": "token is expired"}, 401
status_code=status.HTTP_401_UNAUTHORIZED, detail="token is expired"
)

except Exception as e:
print("135")
raise HTTPException(401, f"Unable to parse authentication token: {e.args}" )
raise HTTPException(401, f"Unable to parse authentication token: {e.args}")

# Check that we all scopes are present
if not payload:
raise HTTPException(status_code=401, detail="Invalid authorization token")

token_scopes = payload.get("scope", "").split()
return payload


def create_access_token(data: dict, expire_seconds: int) -> str:
to_encode = data.copy()
to_encode.update(
{"exp": dt.datetime.utcnow() + dt.timedelta(seconds=expire_seconds)}
)

try:
return jwt.encode(to_encode, JWT_KEY, JWT_KEY_ALGORITHM)
except Exception as e:
raise e


async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
security_scopes: SecurityScopes,
) -> UserInDB:
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = "Bearer"

credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value},
)

try:
payload = jwt.decode(token, JWT_KEY, JWT_KEY_ALGORITHM)
except (jwt.PyJWTError, Exception):
raise credentials_exception

token_username = payload.get("username", None)
token_scopes = payload.get("scopes", [])

if not token_username:
raise credentials_exception

for scope in security_scopes.scopes:
if scope not in token_scopes:
print("155 token issue")
# raise HTTPException(
# {
# "code": "Unauthorized",
# "description": f"You don't have access to this resource. `{' '.join(security_scopes.scopes)}` scopes required",
# },
# 403,
# )
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Not enough permissions. Missing scopes: {security_scopes.scopes}",
headers={"WWW-Authenticate": authenticate_value},
)

return payload
return token_username
46 changes: 33 additions & 13 deletions apiserver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,23 @@
import urllib.parse as parse
import requests

AUTH_URL = os.getenv("AUTH_URL")
TOKEN_URL = os.getenv("TOKEN_URL")
SCOPE = os.getenv("SCOPE")
SCOPE_CLAIM = os.getenv("SCOPE_CLAIM")
USERNAME_CLAIM = os.getenv("USERNAME_CLAIM")
FULLNAME_CLAIM = os.getenv("FULLNAME_CLAIM")
EMAIL_CLAIM = os.getenv("EMAIL_CLAIM")
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
REDIRECT_URI = os.getenv("REDIRECT_URI")

# to get a string like this run:
# openssl rand -hex 32
JWT_KEY = os.getenv("JWT_KEY")
JWT_KEY_ALGORITHM = os.getenv("JWT_KEY_ALGORITHM")
JWT_EXPIRY_SECONDS = int(os.getenv("JWT_EXPIRY_SECONDS", 1800))

AUTH_URL = "http://localhost:18080/realms/fabioworst/protocol/openid-connect/auth"
TOKEN_URL = "http://localhost:18080/realms/fabioworst/protocol/openid-connect/token"
SCOPE = "openid"
CLIENT_ID = "BankApp"
CLIENT_SECRET = "XmdmfRWJ149Iaf054NWU0tZvhIeOYMYz"
REDIRECT_URI = "http://localhost:5500/callback"

ALGORITHM = "RS256"
JWKS = '{"keys":[{"kid":"UFnFt3_8o557r-lH2EuOjXAjzn56Xjv-aWlEsz2C0u0","kty":"RSA","alg":"RSA-OAEP","use":"enc","n":"thdVcAuenPhEBkzSQfrdh50jm3A3swFm-WmE-pQvHvaYafzye3ToFC9vIyNtXUF_p4FLgUJWbUrwZU6PCdEb-S8EPx1x3zUJ9GRas-RG9exhB7RQ3iqQYdEaQzlc7Fzw9nV3OTrmvGz4WdZDZ3FVjGNXx2eNRSJEV4hIS-8Hl2iNAar7w4NV9QhhTHIG6cs_Pp1fnw_buIb_Ap2C1EceGH_xyMSdN2K35exHXhQWUP_4Izw0YYZqM-isBDcUSAsfy2Ae4Sl5rawf-CvRUezE616CHOyAdJtigrwgkKVCr6r2-jgvF-yV0ozKC_SyzK2ZRTC9ChAAo8skO02bxgg-lQ","e":"AQAB","x5c":["MIICozCCAYsCBgGMxsl5ODANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApmYWJpb3dvcnN0MB4XDTI0MDEwMTIwNDcyMVoXDTM0MDEwMTIwNDkwMVowFTETMBEGA1UEAwwKZmFiaW93b3JzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYXVXALnpz4RAZM0kH63YedI5twN7MBZvlphPqULx72mGn88nt06BQvbyMjbV1Bf6eBS4FCVm1K8GVOjwnRG/kvBD8dcd81CfRkWrPkRvXsYQe0UN4qkGHRGkM5XOxc8PZ1dzk65rxs+FnWQ2dxVYxjV8dnjUUiRFeISEvvB5dojQGq+8ODVfUIYUxyBunLPz6dX58P27iG/wKdgtRHHhh/8cjEnTdit+XsR14UFlD/+CM8NGGGajPorAQ3FEgLH8tgHuEpea2sH/gr0VHsxOteghzsgHSbYoK8IJClQq+q9vo4LxfsldKMygv0ssytmUUwvQoQAKPLJDtNm8YIPpUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVbrQ0HCSB4NPvDUv4SPbO7anjRLH/5f/97CXVWymWIwcuvYUpV3ynvmOsg2ygIYkEZKULwlYhlKcGRnbAU1I1wDzPHO8BVCr6Qz8a4olfcL46jppcf3HfLohDPkNp4rpEFwqwBLIGd39Cj5OwB93MrAQ+ZWykdgZV+kHH8BAqfED3m7+wpHjISjkXlAi1IZ52PqvH0NBWdPBznT8tHOCyXbeiMM26oFBNnmZoC95YZA2wUBloF+HNFpvJrUTAPCJk6ekDYLap3wzSJzdF4LgdykGkUzVMOrYOzotOn5nMSP+2oo8toSIpNA2SZ/pM5hAvP2JftBCunxKWgfvohr36g=="],"x5t":"LvaKfBL3DWrhayiw_ApyR3E2Exg","x5t#S256":"pDexnxZVn4tQToZX6ZzHoz7XXVikyKEk_6KJE5Eby1E"},{"kid":"aBeFPPWPSBtx-mHokr8Dox3IrFdSjeLsXG__uVLmkQs","kty":"RSA","alg":"RS256","use":"sig","n":"2Il9H1HC6iSeJmXUuPBSRy3JOGjcYXyrWr-ETqP9lXRUk4tV8jYBLRNnLt6R5YthpB03X5-AAZZXDPnLqIED2lE9rdvXO_D5sHCrgeIWG-bG11LzZS8oRrzeszOEoxYUdr1VB0HT45mElvmBk4OvEjDbjdBFzuARunmmqjfRh327tu4BSn4bseRTMqozDaYJIp78Hh5YZxz9pNaNQWIqPKyjtWg-HLKmQGUcK32dPloqMrwCgjusdO6mO8W6l0H9-g7AvLrcUoss5H5-LJQWQS3T2ZL8-WzbH3Ji6VKqvtiVzgtPuASDh54ToKxHTlmeO2znfqwfADBP3P3apg4DtQ","e":"AQAB","x5c":["MIICozCCAYsCBgGMxsl4nTANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApmYWJpb3dvcnN0MB4XDTI0MDEwMTIwNDcyMVoXDTM0MDEwMTIwNDkwMVowFTETMBEGA1UEAwwKZmFiaW93b3JzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANiJfR9RwuokniZl1LjwUkctyTho3GF8q1q/hE6j/ZV0VJOLVfI2AS0TZy7ekeWLYaQdN1+fgAGWVwz5y6iBA9pRPa3b1zvw+bBwq4HiFhvmxtdS82UvKEa83rMzhKMWFHa9VQdB0+OZhJb5gZODrxIw243QRc7gEbp5pqo30Yd9u7buAUp+G7HkUzKqMw2mCSKe/B4eWGcc/aTWjUFiKjyso7VoPhyypkBlHCt9nT5aKjK8AoI7rHTupjvFupdB/foOwLy63FKLLOR+fiyUFkEt09mS/Pls2x9yYulSqr7Ylc4LT7gEg4eeE6CsR05Znjts536sHwAwT9z92qYOA7UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAchpFWYaMpWy93OoZKrs9IBmJ+7VXZmHk7idl/Y835cQ7oCLzsRH5B9lR4EXz7KNBaJlpTWs8I/uoiKV58LNeKR8VGDf3sMksKqpR2HwE6Ym1YzSMcWloUM829tKjdCfrIoLa6ONKsJpkaEvVriRKAxAf51lvccEiVEhTVpIwhKV892K6pGmSNaiFCJNB7gdFhnZndc/Mdu/1+h0c1apTNckdPnbuXwrXy+dWmPCKQVXfKyWiFtFPfe9nFf4Dx6u24FESwC3g+bjMHV1B364hNKguBZogiEqa5EQGTzDg+akG4qs6cY3HMEv+yLjtHwGEgT6EyFqJa/zEPMcJGVU0Qw=="],"x5t":"wpGV-y-vuLqwylzggZBh2oOKXcM","x5t#S256":"DT74NssiVfgcRxo7_dBsQPGi3Nltupb2yrb5v6W8dek"}]}'


app = FastAPI(
title="Worst API",
Expand Down Expand Up @@ -111,11 +116,26 @@ async def get_token(authorization_code: str):
)

token = r.json()
payload = dep.decode_token(token["id_token"])

except HTTPException as e:
raise e
except Exception as e:
print("Exception: ", e)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e)
print("Exception: ", e.args)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.args
)

user_details = {
"username": payload[USERNAME_CLAIM],
"fullname": payload[FULLNAME_CLAIM],
"email": payload[EMAIL_CLAIM],
"scopes": payload[SCOPE_CLAIM],
}

access_token = dep.create_access_token(user_details, JWT_EXPIRY_SECONDS)

return Token(access_token=token["access_token"], token_type="bearer")
return Token(access_token=access_token, token_type="bearer", user_details=user_details)


# add routers dynamically
Expand Down
1 change: 1 addition & 0 deletions apiserver/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Attachments(BaseModel):
class Token(BaseModel):
access_token: str
token_type: str
user_details: dict


class CommonUser(BaseModel):
Expand Down
5 changes: 1 addition & 4 deletions apiserver/routers/admin/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from fastapi import APIRouter

from . import users, status, models
from . import models

router = APIRouter(prefix="/admin")


router.include_router(users.router)
# router.include_router(status.router)
router.include_router(models.router)
16 changes: 8 additions & 8 deletions apiserver/routers/admin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@

@router.get(
"",
dependencies=[Security(dep.get_current_user, scopes=["ro"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_read"])],
)
async def get_all_models() -> dict[str, Model] | None:
return JSONResponse(jsonable_encoder(svc.get_all_models()))


@router.get(
"/{name}",
dependencies=[Security(dep.get_current_user, scopes=["ro"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_read"])],
)
async def get_model(name: str) -> Model | None:
return svc.get_model(name)


@router.post(
"",
dependencies=[Security(dep.get_current_user, scopes=["admin"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_admin"])],
)
async def create_model(
model: ModelUpdate,
current_user: Annotated[User, Security(dep.get_current_user, scopes=["admin"])],
current_user: Annotated[User, Security(dep.get_current_user, scopes=["worst_admin"])],
bg_task: BackgroundTasks,
) -> Model | None:
x = svc.create_model(model, current_user.user_id)
Expand All @@ -57,11 +57,11 @@ async def create_model(

@router.put(
"",
dependencies=[Security(dep.get_current_user, scopes=["admin"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_admin"])],
)
async def update_model(
model: ModelUpdate,
current_user: Annotated[User, Security(dep.get_current_user, scopes=["admin"])],
current_user: Annotated[User, Security(dep.get_current_user, scopes=["worst_admin"])],
bg_task: BackgroundTasks,
) -> Model | None:
x = svc.update_model(model, current_user.user_id)
Expand All @@ -81,11 +81,11 @@ async def update_model(

@router.delete(
"/{name}",
dependencies=[Security(dep.get_current_user, scopes=["admin"])],
dependencies=[Security(dep.get_current_user, scopes=["worst_admin"])],
)
async def delete_model(
name: str,
current_user: Annotated[User, Security(dep.get_current_user, scopes=["admin"])],
current_user: Annotated[User, Security(dep.get_current_user, scopes=["worst_admin"])],
bg_task: BackgroundTasks,
) -> Model | None:
x = svc.delete_model(name)
Expand Down
Loading

0 comments on commit ed382b1

Please sign in to comment.