Skip to content

Commit 06338f1

Browse files
authored
Added a local identity provider. (#1333)
* Added a local identity provider. * Fixed typo. * Resetting color, which fallsback to a default in UI. * Added fallback user, unit tests. * Removed fallback to base identity provider. * Added fallback to anonymous user.
1 parent d6038af commit 06338f1

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import asyncio
2+
import getpass
3+
4+
from jupyter_server.auth.identity import IdentityProvider, User
5+
6+
7+
def create_initials(username):
8+
"""Creates initials combining first 2 consonants"""
9+
10+
username = username.lower()
11+
12+
# Default: return first two unique consonants
13+
consonants = [c for c in username if c in "bcdfghjklmnpqrstvwxyz"]
14+
if len(consonants) >= 2:
15+
return (consonants[0] + consonants[1]).upper()
16+
17+
# Fallback: first two characters
18+
return username[:2].upper()
19+
20+
21+
class LocalIdentityProvider(IdentityProvider):
22+
"""IdentityProvider that determines username from system user."""
23+
24+
def get_user(self, handler):
25+
try:
26+
username = getpass.getuser()
27+
user = User(
28+
username=username,
29+
name=username,
30+
initials=create_initials(username),
31+
color=None,
32+
)
33+
return user
34+
except OSError as e:
35+
self.log.debug(
36+
"Could not determine username from system. Falling back to anonymous"
37+
f"user."
38+
)
39+
return self._get_user(handler)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import logging
2+
from unittest.mock import Mock, patch
3+
4+
import pytest
5+
from jupyter_ai.auth.identity import LocalIdentityProvider, create_initials
6+
from jupyter_server.auth.identity import User
7+
8+
9+
@pytest.fixture
10+
def log():
11+
log = logging.getLogger()
12+
log.addHandler(logging.NullHandler())
13+
return log
14+
15+
16+
@pytest.fixture
17+
def handler():
18+
return Mock()
19+
20+
21+
@patch("getpass.getuser")
22+
def test_get_user_successful(getuser, log, handler):
23+
24+
getuser.return_value = "localuser"
25+
provider = LocalIdentityProvider(log=log)
26+
27+
user = provider.get_user(handler)
28+
29+
assert isinstance(user, User)
30+
assert user.username == "localuser"
31+
assert user.name == "localuser"
32+
assert user.initials == "LC"
33+
assert user.color is None
34+
35+
36+
@patch("getpass.getuser")
37+
@pytest.mark.asyncio
38+
async def test_get_user_with_error(getuser, log, handler):
39+
40+
getuser.return_value = "localuser"
41+
getuser.side_effect = OSError("Could not get username")
42+
handler._jupyter_current_user = User(username="jupyteruser")
43+
44+
provider = LocalIdentityProvider(log=log)
45+
46+
user = provider.get_user(handler)
47+
user = await user
48+
49+
assert isinstance(user, User)
50+
assert user.username == "jupyteruser"
51+
52+
53+
@pytest.mark.parametrize(
54+
"username,expected_initials",
55+
[
56+
("johndoe", "JH"),
57+
("alice", "LC"),
58+
("xy", "XY"),
59+
("a", "A"),
60+
("SARAH", "SR"),
61+
("john-smith", "JH"),
62+
("john123", "JH"),
63+
("", ""),
64+
],
65+
)
66+
def test_create_initials(username, expected_initials):
67+
"""Test various initials generation scenarios."""
68+
assert create_initials(username) == expected_initials.upper()

0 commit comments

Comments
 (0)