Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
32 changes: 32 additions & 0 deletions src/pwncore/models/responseModels/ctf_ContainerStatusResponse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pydantic import BaseModel
from typing import List, Optional

class CTF_ErrorResponse(BaseModel):
"""
Generic error response
msg_code: 0 (db_error), 2 (ctf_not_found)
"""
msg_code: int

class ContainerStartResponse(BaseModel):
"""
Response for container start operation
msg_code: 3 (success), 7 (already running), 8 (limit reached), 0 (db_error)
"""
msg_code: int
ports: Optional[List[int]] = None
ctf_id: Optional[int] = None

class ContainerStopResponse(BaseModel):
"""
Response for container stop operations
msg_code: 4 (stop success), 5 (stopall success), 6 (not found), 0 (db_error)
"""
msg_code: int

class ContainerPortsResponse(BaseModel):
"""
Response for all open ports of containers
ports: array of port numbers
"""
ports: dict[int, list[int]]
11 changes: 11 additions & 0 deletions src/pwncore/models/responseModels/leaderboard_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pydantic import BaseModel

# defining Pydantic response model
class LeaderboardEntry(BaseModel):
"""
Returns the leaderboard entry for a team.
name: team name
tpoints: total points
"""
name: str
tpoints: int
40 changes: 40 additions & 0 deletions src/pwncore/models/responseModels/preEventCTF_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from pydantic import BaseModel

# pydantic response models
class PreEventFlag(BaseModel):
"""
Response for pre-event flag submission
tag: team tag
flag: flag submitted
email: email of the team
"""
tag: str
flag: str
email: str


class CoinsQuery(BaseModel):
"""
Response for pre-event coins query
tag: team tag
"""
tag: str

class CoinsResponse(BaseModel):
"""
Response for pre-event coins query
coins: total coins earned by the team in pre-event CTFs
"""
coins: int

class FlagSubmissionResponse(BaseModel):
"""
Response for pre-event flag submission
status: bool
coins: total coins earned by the team in pre-event CTFs
"""
status: bool
coins: int

class preEventCTF_ErrorResponse(BaseModel):
msg_code: int
62 changes: 62 additions & 0 deletions src/pwncore/models/responseModels/teamAuthResponse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from pydantic import BaseModel
import typing as t

# defining Pydantic response models
class AuthBody(BaseModel):
"""
Request body for login
name: name of the user
password: password of the user
"""
name: str
password: str


class SignupBody(BaseModel):
name: str
password: str
tags: set[str]


# Response Models
class SignupResponse(BaseModel):
"""
msg_code: 13 (signup_success)
"""
msg_code: t.Literal[13]

class SignupErrorUsersNotFound(BaseModel):
"""
msg_code: 24 (users_not_found)
tags: list[str]
"""
msg_code: t.Literal[24]
tags: list[str]

class SignupErrorUsersInTeam(BaseModel):
"""
msg_code: 20 (user_already_in_team)
tags: list[str]
"""
msg_code: t.Literal[20]
tags: list[str]

class LoginResponse(BaseModel):
"""
msg_code: 15 (login_success)
access_token: JWT access token
token_type: "bearer"
"""
msg_code: t.Literal[15]
access_token: str
token_type: str

class Auth_ErrorResponse(BaseModel):
"""
msg_code can be:
0 (db_error)
17 (team_exists)
10 (team_not_found)
14 (wrong_password)
"""
msg_code: t.Literal[0, 17, 10, 14]
31 changes: 31 additions & 0 deletions src/pwncore/models/responseModels/userManagementResponse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pydantic import BaseModel
import typing as t

class UserAddBody(BaseModel):
"""
Request body for adding a user
tag: team tag
name: name of the user
email: email of the user
phone_num: phone number of the user
"""
tag: str
name: str
email: str
phone_num: str


class UserRemoveBody(BaseModel):
"""
Request body for removing a user
tag: team tag
"""
tag: str


class MessageResponse(BaseModel):
"""
Response for user management operations
msg_code: message code
"""
msg_code: int
30 changes: 26 additions & 4 deletions src/pwncore/routes/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from fastapi import APIRouter, Request, Response
from passlib.hash import bcrypt, bcrypt_sha256
from tortoise.transactions import atomic, in_transaction
from pydantic import BaseModel
from typing import Optional

import pwncore.containerASD as containerASD
from pwncore.config import config
Expand Down Expand Up @@ -51,14 +53,24 @@ async def _del_cont(id: str):
await container.delete()


class AdminResponse(BaseModel):
success: bool
message: Optional[str] = None


@atomic()
@router.get("/union")
@router.get("/union",
response_model=AdminResponse,
response_description="""Successful calculation of team points and coin updates.

Note: Returns msg_code 401 if authentication fails.
""")
async def calculate_team_coins(
response: Response, req: Request
): # Inefficient, anyways will be used only once
if not bcrypt_sha256.verify((await req.body()).strip(), config.admin_hash): # Use config.admin_hash
response.status_code = 401
return
return AdminResponse(success=False, message="Authentication failed")
async with in_transaction():
logging.info("Calculating team points form pre-event CTFs:")
team_ids = await Team.filter().values_list("id", flat=True)
Expand All @@ -82,14 +94,22 @@ async def calculate_team_coins(
logging.info(f"{team.id}) {team.name}: {team.coins}")
await team.save()

return AdminResponse(success=True, message="Team coins updated successfully")

@router.get("/create")

@router.get("/create",
response_model=AdminResponse,
response_description="""Database initialization with sample data.

Note: Returns msg_code 401 if authentication fails.
This endpoint should only be used in development environment.
""")
async def init_db(
response: Response, req: Request
): # Inefficient, anyways will be used only once
if not bcrypt_sha256.verify((await req.body()).strip(), config.admin_hash):
response.status_code = 401
return
return AdminResponse(success=False, message="Authentication failed")
await Problem.create(
name="Invisible-Incursion",
description="Chod de tujhe se na ho paye",
Expand Down Expand Up @@ -207,3 +227,5 @@ async def init_db(
await SolvedProblem.create(team_id=2, problem_id=1)
await SolvedProblem.create(team_id=2, problem_id=2)
await SolvedProblem.create(team_id=1, problem_id=2)

return AdminResponse(success=True, message="Database initialized with sample data")
58 changes: 42 additions & 16 deletions src/pwncore/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
from logging import getLogger

import jwt

from fastapi import APIRouter, Depends, Header, HTTPException, Response
from passlib.hash import bcrypt
from pydantic import BaseModel
from tortoise.transactions import atomic

from pwncore.models.responseModels.teamAuthResponse import (
AuthBody,
SignupBody,
SignupResponse,
SignupErrorUsersNotFound,
SignupErrorUsersInTeam,
LoginResponse,
Auth_ErrorResponse as ErrorResponse
)
from pwncore.config import config
from pwncore.models import Team, User

Expand All @@ -22,24 +32,28 @@
router = APIRouter(prefix="/auth", tags=["auth"])
logger = getLogger(__name__)


class AuthBody(BaseModel):
name: str
password: str


class SignupBody(BaseModel):
name: str
password: str
tags: set[str]


def normalise_tag(tag: str):
return tag.strip().casefold()


@atomic()
@router.post("/signup")
@router.post("/signup",
response_model=SignupResponse,
responses={
406: {"model": ErrorResponse},
404: {"model": SignupErrorUsersNotFound},
401: {"model": SignupErrorUsersInTeam},
500: {"model": ErrorResponse}
},
response_description="""Create a new team with associated members.

msg_codes for Responses:
- 200: Successful signup : 13
- 406: Team already exists: 17
- 404: Users not found: 24
- 401: Users already in team: 20
- 500: Database error: 0
""")
async def signup_team(team: SignupBody, response: Response):
team.name = team.name.strip()
members = set(map(normalise_tag, team.tags))
Expand Down Expand Up @@ -82,8 +96,20 @@ async def signup_team(team: SignupBody, response: Response):
return {"msg_code": config.msg_codes["db_error"]}
return {"msg_code": config.msg_codes["signup_success"]}


@router.post("/login")

@router.post("/login",
response_model=LoginResponse,
responses={
404: {"model": ErrorResponse},
401: {"model": ErrorResponse}
},
response_description="""Authenticate a team and receive a JWT token.

msg_codes for Responses:
- 200: Successful login: 15
- 404: Team not found: 10
- 401: Wrong password: 14
""")
async def team_login(team_data: AuthBody, response: Response):
# TODO: Simplified logic since we're not doing refresh tokens.

Expand Down
Loading
Loading