Skip to content

Commit 79353a0

Browse files
Fix fps-auth-fief (#316)
* Fix fps-auth-fief * Disable fps-webdav
1 parent c6d7f87 commit 79353a0

File tree

4 files changed

+168
-141
lines changed

4 files changed

+168
-141
lines changed
Lines changed: 89 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from dataclasses import dataclass
12
from typing import Any, Dict, List, Optional, Tuple
23

34
from fastapi import Depends, HTTPException, Request, Response, WebSocket, status
@@ -9,88 +10,101 @@
910
from .config import _AuthFiefConfig
1011

1112

12-
class Backend:
13-
def __init__(self, auth_fief_config: _AuthFiefConfig):
14-
class CustomFiefAuth(FiefAuth):
15-
client: FiefAsync
13+
class CustomFiefAuth(FiefAuth):
14+
client: FiefAsync
1615

17-
async def get_unauthorized_response(self, request: Request, response: Response):
18-
redirect_uri = str(request.url_for("auth_callback"))
19-
auth_url = await self.client.auth_url(redirect_uri, scope=["openid"])
20-
raise HTTPException(
21-
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
22-
headers={"Location": auth_url},
23-
)
24-
25-
self.fief = FiefAsync(
26-
auth_fief_config.base_url,
27-
auth_fief_config.client_id,
28-
auth_fief_config.client_secret,
16+
async def get_unauthorized_response(self, request: Request, response: Response):
17+
redirect_uri = str(request.url_for("auth_callback"))
18+
auth_url = await self.client.auth_url(redirect_uri, scope=["openid"])
19+
raise HTTPException(
20+
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
21+
headers={"Location": auth_url},
2922
)
3023

31-
self.SESSION_COOKIE_NAME = "fps_auth_fief_user_session"
32-
scheme = APIKeyCookie(name=self.SESSION_COOKIE_NAME, auto_error=False)
33-
self.auth = CustomFiefAuth(self.fief, scheme)
3424

35-
async def update_user(
36-
user: FiefUserInfo = Depends(self.auth.current_user()),
37-
access_token_info: FiefAccessTokenInfo = Depends(self.auth.authenticated()),
38-
):
39-
async def _(data: Dict[str, Any]) -> FiefUserInfo:
40-
user = await self.fief.update_profile(
41-
access_token_info["access_token"], {"fields": data}
42-
)
43-
return user
44-
45-
return _
46-
47-
def websocket_auth(permissions: Optional[Dict[str, List[str]]] = None):
48-
async def _(
49-
websocket: WebSocket,
50-
) -> Optional[Tuple[WebSocket, Optional[Dict[str, List[str]]]]]:
51-
accept_websocket = False
52-
checked_permissions: Optional[Dict[str, List[str]]] = None
53-
if self.SESSION_COOKIE_NAME in websocket._cookies:
54-
access_token = websocket._cookies[self.SESSION_COOKIE_NAME]
55-
if permissions is None:
56-
accept_websocket = True
57-
else:
58-
checked_permissions = {}
59-
for resource, actions in permissions.items():
60-
allowed = checked_permissions[resource] = []
61-
for action in actions:
62-
try:
63-
await self.fief.validate_access_token(
64-
access_token, required_permissions=[f"{resource}:{action}"]
65-
)
66-
except BaseException:
67-
pass
68-
else:
69-
allowed.append(action)
70-
accept_websocket = True
71-
if accept_websocket:
72-
return websocket, checked_permissions
25+
@dataclass
26+
class Res:
27+
fief: FiefAsync
28+
session_cookie_name: str
29+
auth: CustomFiefAuth
30+
current_user: Any
31+
update_user: Any
32+
websocket_auth: Any
33+
34+
35+
def get_backend(auth_fief_config: _AuthFiefConfig) -> Res:
36+
fief = FiefAsync(
37+
auth_fief_config.base_url,
38+
auth_fief_config.client_id,
39+
auth_fief_config.client_secret,
40+
)
41+
42+
session_cookie_name = "fps_auth_fief_user_session"
43+
scheme = APIKeyCookie(name=session_cookie_name, auto_error=False)
44+
auth = CustomFiefAuth(fief, scheme)
45+
46+
async def update_user(
47+
user: FiefUserInfo = Depends(auth.current_user()),
48+
access_token_info: FiefAccessTokenInfo = Depends(auth.authenticated()),
49+
):
50+
async def _(data: Dict[str, Any]) -> FiefUserInfo:
51+
user = await fief.update_profile(access_token_info["access_token"], {"fields": data})
52+
return user
53+
54+
return _
55+
56+
def websocket_auth(permissions: Optional[Dict[str, List[str]]] = None):
57+
async def _(
58+
websocket: WebSocket,
59+
) -> Optional[Tuple[WebSocket, Optional[Dict[str, List[str]]]]]:
60+
accept_websocket = False
61+
checked_permissions: Optional[Dict[str, List[str]]] = None
62+
if session_cookie_name in websocket._cookies:
63+
access_token = websocket._cookies[session_cookie_name]
64+
if permissions is None:
65+
accept_websocket = True
7366
else:
74-
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
75-
return None
67+
checked_permissions = {}
68+
for resource, actions in permissions.items():
69+
allowed = checked_permissions[resource] = []
70+
for action in actions:
71+
try:
72+
await fief.validate_access_token(
73+
access_token, required_permissions=[f"{resource}:{action}"]
74+
)
75+
except BaseException:
76+
pass
77+
else:
78+
allowed.append(action)
79+
accept_websocket = True
80+
if accept_websocket:
81+
return websocket, checked_permissions
82+
else:
83+
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
84+
return None
7685

77-
return _
86+
return _
7887

79-
def current_user(permissions=None):
80-
if permissions is not None:
81-
permissions = [
82-
f"{resource}:{action}"
83-
for resource, actions in permissions.items()
84-
for action in actions
85-
]
88+
def current_user(permissions=None):
89+
if permissions is not None:
90+
permissions = [
91+
f"{resource}:{action}"
92+
for resource, actions in permissions.items()
93+
for action in actions
94+
]
8695

87-
async def _(
88-
user: FiefUserInfo = Depends(self.auth.current_user(permissions=permissions)),
89-
):
90-
return User(**user["fields"])
96+
async def _(
97+
user: FiefUserInfo = Depends(auth.current_user(permissions=permissions)),
98+
):
99+
return User(**user["fields"])
91100

92-
return _
101+
return _
93102

94-
self.current_user = current_user
95-
self.update_user = update_user
96-
self.websocket_auth = websocket_auth
103+
return Res(
104+
fief=fief,
105+
session_cookie_name=session_cookie_name,
106+
auth=auth,
107+
current_user=current_user,
108+
update_user=update_user,
109+
websocket_auth=websocket_auth,
110+
)

plugins/auth_fief/fps_auth_fief/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from jupyverse_api.app import App
44

55
from .config import _AuthFiefConfig
6-
from .routes import _AuthFief
6+
from .routes import auth_factory
77

88

99
class AuthFiefComponent(Component):
@@ -18,5 +18,5 @@ async def start(
1818

1919
app = await ctx.request_resource(App)
2020

21-
auth_fief = _AuthFief(app, self.auth_fief_config)
21+
auth_fief = auth_factory(app, self.auth_fief_config)
2222
ctx.add_resource(auth_fief, types=Auth)
Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import Dict, List
2+
from typing import Any, Callable, Dict, List, Optional, Tuple
33

44
from fastapi import APIRouter, Depends, Query, Request, Response
55
from fastapi.responses import RedirectResponse
@@ -8,66 +8,81 @@
88
from jupyverse_api.app import App
99
from jupyverse_api.auth import Auth, User
1010

11-
from .backend import Backend
11+
from .backend import get_backend
1212
from .config import _AuthFiefConfig
1313

1414

15-
class _AuthFief(Backend, Auth, Router):
16-
def __init__(
17-
self,
18-
app: App,
19-
auth_fief_config: _AuthFiefConfig,
20-
) -> None:
21-
Router.__init__(self, app)
22-
Backend.__init__(self, auth_fief_config)
23-
24-
router = APIRouter()
25-
26-
@router.get("/auth-callback", name="auth_callback")
27-
async def auth_callback(request: Request, response: Response, code: str = Query(...)):
28-
redirect_uri = str(request.url_for("auth_callback"))
29-
tokens, _ = await self.fief.auth_callback(code, redirect_uri)
30-
31-
response = RedirectResponse(request.url_for("root"))
32-
response.set_cookie(
33-
self.SESSION_COOKIE_NAME,
34-
tokens["access_token"],
35-
max_age=tokens["expires_in"],
36-
httponly=True,
37-
secure=False,
38-
)
39-
40-
return response
41-
42-
@router.get("/api/me")
43-
async def get_api_me(
44-
request: Request,
45-
user: User = Depends(self.current_user()),
46-
access_token_info: FiefAccessTokenInfo = Depends(self.auth.authenticated()),
47-
):
48-
checked_permissions: Dict[str, List[str]] = {}
49-
permissions = json.loads(
50-
dict(request.query_params).get("permissions", "{}").replace("'", '"')
51-
)
52-
if permissions:
53-
user_permissions: Dict[str, List[str]] = {}
54-
for permission in access_token_info["permissions"]:
55-
resource, action = permission.split(":")
56-
if resource not in user_permissions:
57-
user_permissions[resource] = []
58-
user_permissions[resource].append(action)
59-
for resource, actions in permissions.items():
60-
user_resource_permissions = user_permissions.get(resource, [])
61-
allowed = checked_permissions[resource] = []
62-
for action in actions:
63-
if action in user_resource_permissions:
64-
allowed.append(action)
65-
66-
keys = ["username", "name", "display_name", "initials", "avatar_url", "color"]
67-
identity = {k: getattr(user, k) for k in keys}
68-
return {
69-
"identity": identity,
70-
"permissions": checked_permissions,
71-
}
72-
73-
self.include_router(router)
15+
def auth_factory(
16+
app: App,
17+
auth_fief_config: _AuthFiefConfig,
18+
):
19+
backend = get_backend(auth_fief_config)
20+
21+
class _AuthFief(Auth, Router):
22+
def __init__(self) -> None:
23+
super().__init__(app)
24+
25+
router = APIRouter()
26+
27+
@router.get("/auth-callback", name="auth_callback")
28+
async def auth_callback(request: Request, response: Response, code: str = Query(...)):
29+
redirect_uri = str(request.url_for("auth_callback"))
30+
tokens, _ = await backend.fief.auth_callback(code, redirect_uri)
31+
32+
response = RedirectResponse(request.url_for("root"))
33+
response.set_cookie(
34+
backend.session_cookie_name,
35+
tokens["access_token"],
36+
max_age=tokens["expires_in"],
37+
httponly=True,
38+
secure=False,
39+
)
40+
41+
return response
42+
43+
@router.get("/api/me")
44+
async def get_api_me(
45+
request: Request,
46+
user: User = Depends(self.current_user()),
47+
access_token_info: FiefAccessTokenInfo = Depends(backend.auth.authenticated()),
48+
):
49+
checked_permissions: Dict[str, List[str]] = {}
50+
permissions = json.loads(
51+
dict(request.query_params).get("permissions", "{}").replace("'", '"')
52+
)
53+
if permissions:
54+
user_permissions: Dict[str, List[str]] = {}
55+
for permission in access_token_info["permissions"]:
56+
resource, action = permission.split(":")
57+
if resource not in user_permissions:
58+
user_permissions[resource] = []
59+
user_permissions[resource].append(action)
60+
for resource, actions in permissions.items():
61+
user_resource_permissions = user_permissions.get(resource, [])
62+
allowed = checked_permissions[resource] = []
63+
for action in actions:
64+
if action in user_resource_permissions:
65+
allowed.append(action)
66+
67+
keys = ["username", "name", "display_name", "initials", "avatar_url", "color"]
68+
identity = {k: getattr(user, k) for k in keys}
69+
return {
70+
"identity": identity,
71+
"permissions": checked_permissions,
72+
}
73+
74+
self.include_router(router)
75+
76+
def current_user(self, permissions: Optional[Dict[str, List[str]]] = None) -> Callable:
77+
return backend.current_user(permissions)
78+
79+
async def update_user(self, update_user=Depends(backend.update_user)) -> Callable:
80+
return update_user
81+
82+
def websocket_auth(
83+
self,
84+
permissions: Optional[Dict[str, List[str]]] = None,
85+
) -> Callable[[], Tuple[Any, Dict[str, List[str]]]]:
86+
return backend.websocket_auth(permissions)
87+
88+
return _AuthFief()

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ pre-install-commands = [
6767
"pip install -e ./plugins/terminals",
6868
"pip install -e ./plugins/yjs",
6969
"pip install -e ./plugins/resource_usage",
70-
"pip install -e ./plugins/webdav[test]",
7170
]
7271
features = ["test"]
7372

@@ -98,7 +97,7 @@ frontend = ["jupyterlab", "retrolab"]
9897
auth = ["noauth", "auth", "auth_fief"]
9998

10099
[tool.hatch.envs.dev.scripts]
101-
test = "pytest ./tests plugins/webdav/tests -v"
100+
test = "pytest ./tests -v"
102101
lint = [
103102
"black --line-length 100 jupyverse ./plugins",
104103
"isort --profile=black jupyverse ./plugins",
@@ -113,7 +112,6 @@ typecheck0 = """mypy --no-incremental \
113112
./plugins/terminals \
114113
./plugins/yjs \
115114
./plugins/resource_usage \
116-
./plugins/webdav \
117115
"""
118116

119117
[tool.hatch.envs.docs]

0 commit comments

Comments
 (0)