Skip to content

Commit

Permalink
feat(admin): initial attempts to utilize proxy authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
imnotjames committed Apr 4, 2024
1 parent 0042a49 commit 3b4e0f1
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 23 deletions.
22 changes: 19 additions & 3 deletions admin/src/portr_admin/apis/security.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
from typing import Annotated
from fastapi import Cookie, Depends, Header
from fastapi import Depends, Header, Request

from portr_admin.models.auth import Session
from portr_admin.models.user import Role, TeamUser, User
from portr_admin.services.user import get_or_create_user
from portr_admin.utils.exception import PermissionDenied
from portr_admin.config import settings


class NotAuthenticated(Exception):
pass


def get_proxy_auth_email(request: Request) -> str | None:
header = settings.remote_user_header

if header is None:
return None

return request.headers.get(header)

async def get_current_user(
portr_session: Annotated[str | None, Cookie()] = None,
request: Request
) -> User:
proxy_auth_email = get_proxy_auth_email(request)

if proxy_auth_email:
return await get_or_create_user(proxy_auth_email)

portr_session = request.cookies.get("portr_session")

if portr_session is None:
raise NotAuthenticated

Expand Down
6 changes: 4 additions & 2 deletions admin/src/portr_admin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ class Settings(BaseSettings):
use_vite: bool = Field(default=False, alias="PORTR_ADMIN_USE_VITE")
encryption_key: str = Field(alias="PORTR_ADMIN_ENCRYPTION_KEY")

github_client_id: str = Field(alias="PORTR_ADMIN_GITHUB_CLIENT_ID")
github_client_secret: str = Field(alias="PORTR_ADMIN_GITHUB_CLIENT_SECRET")
github_client_id: str = Field(alias="PORTR_ADMIN_GITHUB_CLIENT_ID", default="")
github_client_secret: str = Field(alias="PORTR_ADMIN_GITHUB_CLIENT_SECRET", default="")

remote_user_header: str | None = Field(alias="PORTR_ADMIN_REMOTE_USER_HEADER")

server_url: str
ssh_url: str
Expand Down
13 changes: 5 additions & 8 deletions admin/src/portr_admin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ async def lifespan(app: FastAPI):

@app.get("/")
async def render_index_template(
request: Request,
portr_session: Annotated[str | None, Cookie()] = None,
request: Request
):
try:
user: User = await get_current_user(portr_session)
user: User = await get_current_user(request)
except NotAuthenticated:
return templates.TemplateResponse(
request=request,
Expand All @@ -67,11 +66,10 @@ async def render_index_template(
@app.get("/new-team")
@app.get("/instance-settings")
async def render_index_template_for_setup_route(
request: Request,
portr_session: Annotated[str | None, Cookie()] = None,
request: Request
):
try:
user = await get_current_user(portr_session)
user = await get_current_user(request)
if not user.is_superuser:
return RedirectResponse(url="/")
except NotAuthenticated:
Expand All @@ -98,10 +96,9 @@ async def render_index_template_for_setup_route(
async def render_index_template_for_team_routes(
request: Request,
team: str,
portr_session: Annotated[str | None, Cookie()] = None,
):
try:
user: User = await get_current_user(portr_session)
user: User = await get_current_user(request)
except NotAuthenticated:
next_url = request.url.path + "?" + request.url.query
next_url_encoded = urllib.parse.urlencode({"next": next_url})
Expand Down
35 changes: 25 additions & 10 deletions admin/src/portr_admin/services/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ class EmailFetchError(ServiceError):
pass


async def get_or_create_user(email: str):
has_users = await User.filter().exists()

if not has_users:
# If this is the first user we can create them as a superuser
await User.create(
email=email,
is_superuser=True,
)

user = await User.get_or_none(email=email)

if not user:
raise UserNotFoundError("User does not exist yet")

is_user_on_team = await TeamUser.filter(user__email=email).exists()

if not user.is_superuser and not is_user_on_team:
# This user MUST be part of a team to authenticate UNLESS they're a superuser
raise UserNotFoundError("User not part of any team")

return user


@transactions.atomic()
async def get_or_create_user_from_github(code: str):
client = GithubOauth(
Expand All @@ -35,16 +59,7 @@ async def get_or_create_user_from_github(code: str):
if not github_user["email"]:
raise EmailFetchError("No verified email found")

is_superuser = await User.filter().count() == 0

if not is_superuser:
if not await TeamUser.filter(user__email=github_user["email"]).exists():
raise UserNotFoundError("User not part of any team")

user, _ = await User.get_or_create(
email=github_user["email"],
defaults={"is_superuser": is_superuser},
)
user = await get_or_create_user(github_user["email"])

github_user_obj, created = await GithubUser.get_or_create(
user=user,
Expand Down

0 comments on commit 3b4e0f1

Please sign in to comment.