Skip to content

Commit 6f20d2a

Browse files
authored
Merge pull request #147 from mariodruiz/github-fix
fix(hub): decouple GitHub username prefix from login_service label
2 parents 0a0ec63 + 779f799 commit 6f20d2a

6 files changed

Lines changed: 30 additions & 13 deletions

File tree

runtime/hub/core/authenticators/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
from core.authenticators.auto_login import AutoLoginAuthenticator
2727
from core.authenticators.firstuse import CustomFirstUseAuthenticator
28-
from core.authenticators.github_app import CustomGitHubOAuthenticator
28+
from core.authenticators.github_app import GITHUB_USERNAME_PREFIX, CustomGitHubOAuthenticator
2929
from core.authenticators.jwt import RemoteLabAuthenticator
3030
from core.authenticators.multi import CustomMultiAuthenticator
3131

@@ -64,4 +64,5 @@ def create_authenticator(auth_mode: str, **kwargs):
6464
"CustomMultiAuthenticator",
6565
"create_authenticator",
6666
"LOCAL_ACCOUNT_PREFIX",
67+
"GITHUB_USERNAME_PREFIX",
6768
]

runtime/hub/core/authenticators/github_app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
log = logging.getLogger("jupyterhub.auth.github")
3737

38+
GITHUB_USERNAME_PREFIX = "github:"
39+
3840

3941
class _GitHubAppInstallCallbackHandler(OAuthCallbackHandler):
4042
"""Callback handler that gracefully handles GitHub App installation redirects.
@@ -57,6 +59,7 @@ class CustomGitHubOAuthenticator(GitHubOAuthenticator):
5759
"""GitHub App authenticator with access token preservation and refresh."""
5860

5961
name = "github"
62+
prefix = GITHUB_USERNAME_PREFIX
6063
callback_handler = _GitHubAppInstallCallbackHandler
6164

6265
app_id = Unicode(

runtime/hub/core/groups.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
from jupyterhub.user import User as JupyterHubUser
4040
from sqlalchemy.orm import Session
4141

42+
from core.authenticators.github_app import GITHUB_USERNAME_PREFIX
43+
4244
log = logging.getLogger("jupyterhub.groups")
4345

4446
GITHUB_TEAM_SOURCE = "github-team"
@@ -548,7 +550,7 @@ async def sync_github_teams_for_user(
548550
protection. Concurrent spawns for the same user coalesce into one set of
549551
GitHub team membership checks within the TTL window.
550552
"""
551-
if not user.name.startswith("github:") or not app_id:
553+
if not user.name.startswith(GITHUB_USERNAME_PREFIX) or not app_id:
552554
return False
553555

554556
lock = _GITHUB_TEAM_SYNC_LOCKS.setdefault(user.name, asyncio.Lock())
@@ -714,7 +716,7 @@ def resolve_resources_for_user(
714716
if available_resources:
715717
return available_resources
716718

717-
if not username.startswith("github:"):
719+
if not username.startswith(GITHUB_USERNAME_PREFIX):
718720
return team_resource_mapping.get("native-users", team_resource_mapping.get("official", []))
719721

720722
return ["none"]

runtime/hub/core/handlers.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from pydantic import ValidationError
4242
from tornado import web
4343

44-
from core.authenticators import CustomFirstUseAuthenticator
44+
from core.authenticators import GITHUB_USERNAME_PREFIX, CustomFirstUseAuthenticator
4545
from core.git_validation import validate_and_sanitize_repo_url
4646
from core.notifications import get_normalized_notifications
4747
from core.quota import (
@@ -265,7 +265,7 @@ def _render_error(msg: str):
265265
return self.finish(html)
266266

267267
username = user.name
268-
if username.startswith("github:"):
268+
if username.startswith(GITHUB_USERNAME_PREFIX):
269269
html = await _render_error("GitHub users cannot change password here")
270270
self.set_status(400)
271271
return self.finish(html)
@@ -322,7 +322,7 @@ async def get(self):
322322
from jupyterhub.orm import User
323323

324324
for user in self.db.query(User).all():
325-
if not user.name.startswith("github:") and user.name != "admin":
325+
if not user.name.startswith(GITHUB_USERNAME_PREFIX) and user.name != "admin":
326326
native_users.append(user.name)
327327

328328
html = await self.render_template(
@@ -356,7 +356,7 @@ async def post(self):
356356
)
357357

358358
username = target_user
359-
if username.startswith("github:"):
359+
if username.startswith(GITHUB_USERNAME_PREFIX):
360360
return self.redirect(
361361
self.hub.base_url
362362
+ f"admin/reset-password?user={target_user}&error=Cannot+reset+password+for+GitHub+users"
@@ -438,7 +438,7 @@ async def post(self):
438438
self.set_header("Content-Type", "application/json")
439439
return self.finish(json.dumps({"error": "Username and password are required"}))
440440

441-
if username.startswith("github:"):
441+
if username.startswith(GITHUB_USERNAME_PREFIX):
442442
self.set_status(400)
443443
self.set_header("Content-Type", "application/json")
444444
return self.finish(json.dumps({"error": "Cannot set password for GitHub users"}))
@@ -532,7 +532,7 @@ async def post(self):
532532
self.set_status(400)
533533
self.set_header("Content-Type", "application/json")
534534
return self.finish(json.dumps({"error": "Each entry must have username and password"}))
535-
if entry.get("username", "").startswith("github:"):
535+
if entry.get("username", "").startswith(GITHUB_USERNAME_PREFIX):
536536
self.set_status(400)
537537
self.set_header("Content-Type", "application/json")
538538
return self.finish(
@@ -1593,7 +1593,7 @@ async def post(self):
15931593
skipped = 0
15941594

15951595
for user in self.users.values():
1596-
if not user.name.startswith("github:"):
1596+
if not user.name.startswith(GITHUB_USERNAME_PREFIX):
15971597
skipped += 1
15981598
continue
15991599

runtime/hub/core/setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def setup_hub(c: Any) -> None:
6565
"""
6666
from core import z2jh
6767
from core.authenticators import (
68+
GITHUB_USERNAME_PREFIX,
6869
CustomFirstUseAuthenticator,
6970
CustomGitHubOAuthenticator,
7071
create_authenticator,
@@ -121,7 +122,7 @@ async def auth_state_hook(spawner, auth_state):
121122
if auth_state is None:
122123
spawner.github_access_token = None
123124
# Still assign native users to their default group
124-
if not spawner.user.name.startswith("github:"):
125+
if not spawner.user.name.startswith(GITHUB_USERNAME_PREFIX):
125126
try:
126127
from core.groups import assign_user_to_group
127128

@@ -131,7 +132,7 @@ async def auth_state_hook(spawner, auth_state):
131132
return
132133
spawner.github_access_token = auth_state.get("access_token")
133134

134-
if spawner.user.name.startswith("github:"):
135+
if spawner.user.name.startswith(GITHUB_USERNAME_PREFIX):
135136
try:
136137
from core.groups import sync_github_teams_for_user
137138

@@ -160,7 +161,7 @@ async def auth_state_hook(spawner, auth_state):
160161
assign_user_to_group(spawner.user, "github-users", spawner.user.db)
161162
except Exception as e:
162163
print(f"[GROUPS] Warning: Failed to assign github-users group for {spawner.user.name}: {e}")
163-
elif not spawner.user.name.startswith("github:"):
164+
elif not spawner.user.name.startswith(GITHUB_USERNAME_PREFIX):
164165
# Native user with auth_state but no GitHub teams
165166
try:
166167
from core.groups import assign_user_to_group

runtime/hub/tests/test_groups.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
core_module.__path__ = [str(CORE)]
5252
sys.modules["core"] = core_module
5353

54+
if "core.authenticators" not in sys.modules:
55+
authenticators_module = types.ModuleType("core.authenticators")
56+
authenticators_module.__path__ = [str(CORE / "authenticators")]
57+
sys.modules["core.authenticators"] = authenticators_module
58+
59+
if "core.authenticators.github_app" not in sys.modules:
60+
github_app_module = types.ModuleType("core.authenticators.github_app")
61+
github_app_module.GITHUB_USERNAME_PREFIX = "github:"
62+
sys.modules["core.authenticators.github_app"] = github_app_module
63+
5464

5565
def load_module(name: str, path: Path):
5666
spec = importlib.util.spec_from_file_location(name, path)

0 commit comments

Comments
 (0)