diff --git a/config.py b/config.py index ff52225..648a5ff 100644 --- a/config.py +++ b/config.py @@ -1,16 +1,24 @@ from pathlib import Path import tomllib +from types import SimpleNamespace +import sys -with open(Path.home() / ".o365-oauth-config.toml", "rb") as f: - config_data = tomllib.load(f) -cache_path = Path.home() / ".chache/o365-oauth" -cache_path.mkdir(parents=True, exist_ok=True) +def get_config(profile): + with open(Path.home() / ".o365-auth-config.toml", "rb") as f: + toplevel_data = tomllib.load(f) -ClientId = config_data["ClientId"] -ClientSecret = config_data["ClientSecret"] -Scopes = config_data["Scopes"] -RefreshTokenFileName = cache_path / "imap_smtp_refresh_token" -AccessTokenFileName = cache_path / "imap_smtp_access_token" + if profile not in toplevel_data: + sys.exit("Invalid profile specified.") -Authority = config_data["Authority"] or None + config_data = toplevel_data[profile] + cache_path = Path.home() / ".cache/o365-oauth" / profile + cache_path.mkdir(parents=True, exist_ok=True) + + return SimpleNamespace( + ClientId = config_data["ClientId"], + ClientSecret = config_data["ClientSecret"], + Scopes = config_data["Scopes"], + RefreshTokenFileName = cache_path / "imap_smtp_refresh_token", + AccessTokenFileName = cache_path / "imap_smtp_access_token", + Authority = config_data["Authority"] or None) diff --git a/flake.lock b/flake.lock index 7765e58..c1f2ab7 100644 --- a/flake.lock +++ b/flake.lock @@ -13,61 +13,11 @@ "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "locked": { - "lastModified": 1642700792, - "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "mach-nix": { - "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs", - "pypi-deps-db": "pypi-deps-db" - }, - "locked": { - "lastModified": 1705470643, - "narHash": "sha256-CqgkRcvOonJ2fL6542/ykZR3yL6qVLWy0pdyY5YKRlE=", - "owner": "DavHau", - "repo": "mach-nix", - "rev": "28f563aeb8c9e679a3f2b531c728573fce7a4594", - "type": "github" - }, - "original": { - "id": "mach-nix", + "id": "flake-utils", "type": "indirect" } }, "nixpkgs": { - "locked": { - "lastModified": 1643805626, - "narHash": "sha256-AXLDVMG+UaAGsGSpOtQHPIKB+IZ0KSd9WS77aanGzgc=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "554d2d8aa25b6e583575459c297ec23750adb6cb", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" - } - }, - "nixpkgs_2": { "locked": { "lastModified": 1712192574, "narHash": "sha256-LbbVOliJKTF4Zl2b9salumvdMXuQBr2kuKP5+ZwbYq4=", @@ -83,27 +33,11 @@ "type": "github" } }, - "pypi-deps-db": { - "flake": false, - "locked": { - "lastModified": 1685526402, - "narHash": "sha256-V0SXx0dWlUBL3E/wHWTszrkK2dOnuYYnBc7n6e0+NQU=", - "owner": "DavHau", - "repo": "pypi-deps-db", - "rev": "ba35683c35218acb5258b69a9916994979dc73a9", - "type": "github" - }, - "original": { - "owner": "DavHau", - "repo": "pypi-deps-db", - "type": "github" - } - }, "root": { "inputs": { "flake-utils": "flake-utils", - "mach-nix": "mach-nix", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs", + "systems": "systems_2" } }, "systems": { @@ -120,6 +54,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 38dc0ef..732ff1d 100644 --- a/flake.nix +++ b/flake.nix @@ -3,29 +3,35 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + systems.url = "github:nix-systems/default"; }; - outputs = { nixpkgs, flake-utils, mach-nix, ... }: + outputs = { self, nixpkgs, flake-utils, systems, ... }: + let + eachSystem = nixpkgs.lib.genAttrs (import systems); + in + { + homeManagerModules.default = import ./nix/hm-module.nix self; - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - in - with pkgs.python3Packages; { - packages.default = pkgs.stdenv.mkDerivation { - name = "myscript"; - propagatedBuildInputs = [ - (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [ - msal - ])) - ]; - dontUnpack = true; - installPhase = '' - install -Dm755 ${./get_token.py} $out/bin/o365-get-token - install -Dm755 ${./refresh_token.py} $out/bin/o365-refresh-token - install -Dm755 ${./config.py} $out/bin/config.py - ''; - }; - homeManagerModules.default = import ./nix/hm-module.nix self; - }); + + packages = eachSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + with pkgs.python3Packages; { + default = pkgs.stdenv.mkDerivation { + name = "o365-auth"; + propagatedBuildInputs = [ + (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [ + msal + ])) + ]; + dontUnpack = true; + installPhase = '' + install -Dm755 ${./get_token.py} $out/bin/o365-get-token + install -Dm755 ${./refresh_token.py} $out/bin/o365-refresh-token + install -Dm755 ${./config.py} $out/bin/config.py + ''; + }; + }); + }; } diff --git a/get_token.py b/get_token.py index d1d66f9..296cdf4 100755 --- a/get_token.py +++ b/get_token.py @@ -8,14 +8,22 @@ import threading import urllib.parse import webbrowser +if len(sys.argv) > 1: + profile = sys.argv[1] +else: + sys.exit("Please provide a profile name as the first argument.") + + +profile_config = config.get_config(profile) + redirect_uri = "http://localhost:8745/" # We use the cache to extract the refresh token cache = SerializableTokenCache() -app = ConfidentialClientApplication(config.ClientId, client_credential=config.ClientSecret, token_cache=cache, authority=config.Authority) +app = ConfidentialClientApplication(profile_config.ClientId, client_credential=profile_config.ClientSecret, token_cache=cache, authority=profile_config.Authority) -url = app.get_authorization_request_url(config.Scopes, redirect_uri=redirect_uri) +url = app.get_authorization_request_url(profile_config.Scopes, redirect_uri=redirect_uri) # webbrowser.open may fail silently print("Navigate to the following url in a web browser, if doesn't open automatically:") @@ -62,7 +70,7 @@ if code == '': i = resp.find('code') + 5 code = resp[i : resp.find('&', i)] if i > 4 else resp -token = app.acquire_token_by_authorization_code(code, config.Scopes, redirect_uri=redirect_uri) +token = app.acquire_token_by_authorization_code(code, profile_config.Scopes, redirect_uri=redirect_uri) print() @@ -70,10 +78,10 @@ if 'error' in token: print(token) sys.exit("Failed to get access token") -with open(config.RefreshTokenFileName, 'w') as f: - print(f'Refresh token acquired, writing to file {config.RefreshTokenFileName}') +with open(profile_config.RefreshTokenFileName, 'w') as f: + print(f'Refresh token acquired, writing to file {profile_config.RefreshTokenFileName}') f.write(token['refresh_token']) -with open(config.AccessTokenFileName, 'w') as f: - print(f'Access token acquired, writing to file {config.AccessTokenFileName}') +with open(profile_config.AccessTokenFileName, 'w') as f: + print(f'Access token acquired, writing to file {profile_config.AccessTokenFileName}') f.write(token['access_token']) diff --git a/nix/hm-module.nix b/nix/hm-module.nix index c8cec82..4e9634c 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -1,33 +1,35 @@ -self: { - config, - lib, - pkgs, - ... -}: +self: { config + , lib + , pkgs + , ... + }: with lib; let inherit (pkgs.stdenv.hostPlatform) system; package = self.packages.${system}.default; cfg = config.programs.o365-auth; -in { +in +{ options.programs.o365-auth = { enable = mkEnableOption "o365 token refresh script"; + package = mkOption { + type = types.package; + default = package; + }; config = mkOption { type = types.str; default = '' -ClientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584" -ClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82" -Scopes = ['https://outlook.office.com/IMAP.AccessAsUser.All','https://outlook.office.com/SMTP.Send'] -RefreshTokenFileName = "imap_smtp_refresh_token" -AccessTokenFileName = "imap_smtp_access_token" - -Authority = false -''; + [mcgill] + ClientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584" + ClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82" + Scopes = ['https://outlook.office.com/IMAP.AccessAsUser.All','https://outlook.office.com/SMTP.Send'] + Authority = false + ''; }; }; config = mkIf cfg.enable { - home.packages = [ package ]; - home.file.".o365-auth-config".source = cfg.config; + home.packages = [ cfg.package ]; + home.file.".o365-auth-config".text = cfg.config; }; } diff --git a/refresh_token.py b/refresh_token.py index 30dd2b1..41d165a 100644 --- a/refresh_token.py +++ b/refresh_token.py @@ -3,27 +3,41 @@ from msal import ConfidentialClientApplication, SerializableTokenCache import config import sys -print_access_token = True +print_access_token = True +# get first command line argument + +if len(sys.argv) > 1: + profile = sys.argv[1] +else: + sys.exit("Please provide a profile name as the first argument.") + + +profile_config = config.get_config(profile) # We use the cache to extract the refresh token cache = SerializableTokenCache() -app = ConfidentialClientApplication(config.ClientId, client_credential=config.ClientSecret, token_cache=cache, authority=config.Authority) +app = ConfidentialClientApplication(profile_config.ClientId, client_credential=profile_config.ClientSecret, token_cache=cache, authority=profile_config.Authority) -old_refresh_token = open(config.RefreshTokenFileName,'r').read() +# check if file exists and error out if it doesn't +try: + old_refresh_token = open(profile_config.RefreshTokenFileName,'r').read() +except FileNotFoundError: + sys.exit("Please get the initial token by running `o365-get-token` first.") -token = app.acquire_token_by_refresh_token(old_refresh_token,config.Scopes) + +token = app.acquire_token_by_refresh_token(old_refresh_token,profile_config.Scopes) if 'error' in token: print(token) sys.exit("Failed to get access token") # you're supposed to save the old refresh token each time -with open(config.RefreshTokenFileName, 'w') as f: +with open(profile_config.RefreshTokenFileName, 'w') as f: #f.write(cache.find('RefreshToken')[0]['secret']) f.write(token['refresh_token']) -with open(config.AccessTokenFileName, 'w') as f: +with open(profile_config.AccessTokenFileName, 'w') as f: f.write(token['access_token']) if print_access_token: print(token['access_token'])