diff --git a/auth_server/auth.py b/auth_server/auth.py index 3f31cc2..fc48fd8 100644 --- a/auth_server/auth.py +++ b/auth_server/auth.py @@ -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}'") @@ -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: @@ -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") @@ -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 @@ -175,4 +174,4 @@ def create_token(user: schema.User) -> str: algorithm=settings.papermerge__security__token_algorithm, ) - return access_token + return access_token \ No newline at end of file diff --git a/auth_server/backends/oidc.py b/auth_server/backends/oidc.py index cbe6ac5..9895596 100644 --- a/auth_server/backends/oidc.py +++ b/auth_server/backends/oidc.py @@ -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__( @@ -18,7 +16,9 @@ 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 @@ -26,24 +26,29 @@ def __init__( 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: @@ -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): @@ -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( @@ -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, @@ -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: @@ -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 diff --git a/auth_server/config.py b/auth_server/config.py index 6d59064..97fee00 100644 --- a/auth_server/config.py +++ b/auth_server/config.py @@ -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 @@ -49,4 +52,4 @@ class Settings(BaseSettings): @lru_cache() def get_settings(): - return Settings() + return Settings() \ No newline at end of file