Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
7 changes: 3 additions & 4 deletions auth_server/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def create_access_token(

def db_auth(session: Session, username: str, password: str) -> schema.User | None:
"""Authenticates user based on username and password

User data is read from database.
"""
logger.info(f"Database based authentication for '{username}'")
Expand Down Expand Up @@ -127,7 +126,6 @@ async def ldap_auth(

return dbapi.get_or_create_user_by_email(session, email)


async def oidc_auth(
session: Session, client_id: str, code: str, redirect_url: str
) -> str | None:
Expand All @@ -141,6 +139,8 @@ async def oidc_auth(
client_id=client_id,
code=code,
redirect_url=redirect_url,
scope=settings.papermerge__auth__oidc_scope,
tenant_id=settings.papermerge__auth__oidc_tenant_id,
)

logger.debug("Auth:oidc: sign in")
Expand All @@ -156,7 +156,6 @@ async def oidc_auth(

return result


def create_token(user: schema.User) -> str:
access_token_expires = timedelta(
minutes=settings.papermerge__security__token_expire_minutes
Expand All @@ -175,4 +174,4 @@ def create_token(user: schema.User) -> str:
algorithm=settings.papermerge__security__token_algorithm,
)

return access_token
return access_token
51 changes: 37 additions & 14 deletions auth_server/backends/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

class OIDCAuth:
name: str = 'oidc'
# provider_url: str = 'https://oauth2.googleapis.com/token'
# userinfo_url: str = 'https://www.googleapis.com/oauth2/v3/userinfo'
access_token: str | None = None

def __init__(
Expand All @@ -18,32 +16,39 @@ def __init__(
client_secret: str,
client_id: str,
code: str,
redirect_url: str
redirect_url: str,
scope: str | None = None,
tenant_id: str | None = None
):
self.access_token_url = access_token_url
self.user_info_url = user_info_url
self.client_secret = client_secret
self.client_id = client_id
self.code = code
self.redirect_url = redirect_url
self.scope = scope or "openid profile email"
self.tenant_id = tenant_id

async def signin(self):
async with httpx.AsyncClient() as client:
params = {
# Entra ID requires credentials in the request body as form data
# not as URL parameters
data = {
'grant_type': 'authorization_code',
'client_id': self.client_id,
'client_secret': self.client_secret,
# do we need this param?
'redirect_uri': self.redirect_url,
'code': self.code,
'scope': self.scope,
}
logger.debug(f"oidc signin params: {params}")

logger.debug(f"oidc signin with client_id: {self.client_id}")
logger.debug(f"oidc signin url: {self.access_token_url}")

try:
response = await client.post(
self.access_token_url,
params=params,
data=params,
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
except Exception as ex:
Expand All @@ -62,7 +67,12 @@ async def signin(self):
])
raise ValueError(message)

self.access_token = response.json()['access_token']
response_data = response.json()
self.access_token = response_data.get('access_token')

if not self.access_token:
raise ValueError("No access_token in response")

return self.access_token

async def user_email(self):
Expand All @@ -88,7 +98,21 @@ async def user_email(self):
])
raise ValueError(message)

return response.json()['email']
user_info = response.json()

# Entra ID uses different claim names
# Try multiple fields for email in order of preference
email = (
user_info.get('email') or
user_info.get('preferred_username') or
user_info.get('upn') or
user_info.get('unique_name')
)

if not email:
raise ValueError(f"No email found in user info response: {user_info}")

return email


async def introspect_token(
Expand All @@ -105,7 +129,7 @@ async def introspect_token(
"""
ret_value = False
async with httpx.AsyncClient() as client:
params = {
data = {
'token': token,
'client_id': client_id,
'client_secret': client_secret,
Expand All @@ -114,8 +138,7 @@ async def introspect_token(
try:
response = await client.post(
url,
params=params,
data=params,
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
except Exception as ex:
Expand All @@ -129,6 +152,6 @@ async def introspect_token(
])
raise ValueError(message)

ret_value = response.json()['active']
ret_value = response.json().get('active', False)

return ret_value
5 changes: 4 additions & 1 deletion auth_server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class Settings(BaseSettings):
papermerge__auth__oidc_user_info_url: str | None = None
# https://datatracker.ietf.org/doc/html/rfc7662
papermerge__auth__oidc_introspect_url: str | None = None
# Entra ID specific settings
papermerge__auth__oidc_tenant_id: str | None = None
papermerge__auth__oidc_scope: str | None = None

papermerge__auth__ldap_url: str | None = None # e.g. ldap.trusel.net
papermerge__auth__ldap_use_ssl: bool = True
Expand All @@ -49,4 +52,4 @@ class Settings(BaseSettings):

@lru_cache()
def get_settings():
return Settings()
return Settings()