diff --git a/truewiki/__main__.py b/truewiki/__main__.py index e83072d..7497e38 100644 --- a/truewiki/__main__.py +++ b/truewiki/__main__.py @@ -21,6 +21,7 @@ from .storage.local import click_storage_local from .user.github import click_user_github from .user.gitlab import click_user_gitlab +from .user.microsoft import click_user_microsoft from .user_session import ( click_user_session, get_user_by_bearer, @@ -122,6 +123,7 @@ async def wait_for_storage(): @click_user_session @click_user_github @click_user_gitlab +@click_user_microsoft @click_page @click.option("--validate-all", help="Validate all mediawiki files and report all errors", is_flag=True) def main(bind, port, storage, frontend_url, cache_time, validate_all): diff --git a/truewiki/user/base_oauth2.py b/truewiki/user/base_oauth2.py index 5f4a7c2..55a1000 100644 --- a/truewiki/user/base_oauth2.py +++ b/truewiki/user/base_oauth2.py @@ -93,7 +93,8 @@ async def get_user_information(self, code): user, _ = await self._oauth2.user_info() - self.display_name = user.username + self.display_name = user.username or user.email + self.email = user.email self.id = str(user.id) return True diff --git a/truewiki/user/microsoft.py b/truewiki/user/microsoft.py new file mode 100644 index 0000000..ca95280 --- /dev/null +++ b/truewiki/user/microsoft.py @@ -0,0 +1,57 @@ +import click + +from aiohttp import web +from aioauth_client import MicrosoftClient +from openttd_helpers import click_helper +from typing import Tuple + +from .base_oauth2 import User as BaseUser + + +MICROSOFT_CLIENT_ID = None +MICROSOFT_CLIENT_SECRET = None + + +@click_helper.extend +@click.option("--user-microsoft-client-id", help="Microsoft client ID. (user=microsoft only)") +@click.option( + "--user-microsoft-client-secret", + help="Microsoft client secret. Always use this via an environment variable! (user=microsoft only)", +) +def click_user_microsoft(user_microsoft_client_id, user_microsoft_client_secret): + global MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET + + MICROSOFT_CLIENT_ID = user_microsoft_client_id + MICROSOFT_CLIENT_SECRET = user_microsoft_client_secret + + +class User(BaseUser): + routes = web.RouteTableDef() + method = "microsoft" + scope = "https://graph.microsoft.com/User.Read" + + def __init__(self, redirect_uri): + super().__init__(redirect_uri) + + if not MICROSOFT_CLIENT_ID or not MICROSOFT_CLIENT_SECRET: + raise Exception("MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET should be set via environment") + + self._oauth2 = MicrosoftClient(client_id=MICROSOFT_CLIENT_ID, client_secret=MICROSOFT_CLIENT_SECRET) + + def get_git_author(self) -> Tuple[str, str]: + # Microsoft doesn't supply anonymous email address to refer to a user. + return (self.display_name, self.email) + + @staticmethod + @routes.get("/user/microsoft-callback") + async def login_microsoft_callback(request): + return await BaseUser.login_oauth2_callback(request) + + @staticmethod + def get_description(): + return "Login via Microsoft" + + @staticmethod + def get_settings_url(): + # Microsoft doesn't appear to have a single settings page to revoke app permissions. + return "" diff --git a/truewiki/user_session.py b/truewiki/user_session.py index c234e07..ced8800 100644 --- a/truewiki/user_session.py +++ b/truewiki/user_session.py @@ -148,7 +148,7 @@ async def user_logout(request): @click.option( "--user", help="User backend to use (can have multiple).", - type=click.Choice(["developer", "github", "gitlab"], case_sensitive=False), + type=click.Choice(["developer", "github", "gitlab", "microsoft"], case_sensitive=False), required=True, multiple=True, callback=click_helper.import_module("truewiki.user", "User"),