implement profiles

This commit is contained in:
Valentin Boettcher 2024-04-05 18:47:33 -04:00
parent c9bdbb305c
commit f85a1900b7
No known key found for this signature in database
GPG key ID: E034E12B7AF56ACE
6 changed files with 119 additions and 132 deletions

View file

@ -1,16 +1,24 @@
from pathlib import Path from pathlib import Path
import tomllib 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" def get_config(profile):
cache_path.mkdir(parents=True, exist_ok=True) with open(Path.home() / ".o365-auth-config.toml", "rb") as f:
toplevel_data = tomllib.load(f)
ClientId = config_data["ClientId"] if profile not in toplevel_data:
ClientSecret = config_data["ClientSecret"] sys.exit("Invalid profile specified.")
Scopes = config_data["Scopes"]
RefreshTokenFileName = cache_path / "imap_smtp_refresh_token"
AccessTokenFileName = cache_path / "imap_smtp_access_token"
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)

87
flake.lock generated
View file

@ -13,61 +13,11 @@
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "id": "flake-utils",
"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",
"type": "indirect" "type": "indirect"
} }
}, },
"nixpkgs": { "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": { "locked": {
"lastModified": 1712192574, "lastModified": 1712192574,
"narHash": "sha256-LbbVOliJKTF4Zl2b9salumvdMXuQBr2kuKP5+ZwbYq4=", "narHash": "sha256-LbbVOliJKTF4Zl2b9salumvdMXuQBr2kuKP5+ZwbYq4=",
@ -83,27 +33,11 @@
"type": "github" "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": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"mach-nix": "mach-nix", "nixpkgs": "nixpkgs",
"nixpkgs": "nixpkgs_2" "systems": "systems_2"
} }
}, },
"systems": { "systems": {
@ -120,6 +54,21 @@
"repo": "default", "repo": "default",
"type": "github" "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", "root": "root",

View file

@ -3,29 +3,35 @@
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 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 packages = eachSystem (system:
pkgs = import nixpkgs { inherit system; }; let
in pkgs = import nixpkgs { inherit system; };
with pkgs.python3Packages; { in
packages.default = pkgs.stdenv.mkDerivation { with pkgs.python3Packages; {
name = "myscript"; default = pkgs.stdenv.mkDerivation {
propagatedBuildInputs = [ name = "o365-auth";
(pkgs.python3.withPackages (pythonPackages: with pythonPackages; [ propagatedBuildInputs = [
msal (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
])) msal
]; ]))
dontUnpack = true; ];
installPhase = '' dontUnpack = true;
install -Dm755 ${./get_token.py} $out/bin/o365-get-token installPhase = ''
install -Dm755 ${./refresh_token.py} $out/bin/o365-refresh-token install -Dm755 ${./get_token.py} $out/bin/o365-get-token
install -Dm755 ${./config.py} $out/bin/config.py 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; };
}); });
};
} }

View file

@ -8,14 +8,22 @@ import threading
import urllib.parse import urllib.parse
import webbrowser 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/" redirect_uri = "http://localhost:8745/"
# We use the cache to extract the refresh token # We use the cache to extract the refresh token
cache = SerializableTokenCache() 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 # webbrowser.open may fail silently
print("Navigate to the following url in a web browser, if doesn't open automatically:") 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 i = resp.find('code') + 5
code = resp[i : resp.find('&', i)] if i > 4 else resp 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() print()
@ -70,10 +78,10 @@ if 'error' in token:
print(token) print(token)
sys.exit("Failed to get access token") sys.exit("Failed to get access token")
with open(config.RefreshTokenFileName, 'w') as f: with open(profile_config.RefreshTokenFileName, 'w') as f:
print(f'Refresh token acquired, writing to file {config.RefreshTokenFileName}') print(f'Refresh token acquired, writing to file {profile_config.RefreshTokenFileName}')
f.write(token['refresh_token']) f.write(token['refresh_token'])
with open(config.AccessTokenFileName, 'w') as f: with open(profile_config.AccessTokenFileName, 'w') as f:
print(f'Access token acquired, writing to file {config.AccessTokenFileName}') print(f'Access token acquired, writing to file {profile_config.AccessTokenFileName}')
f.write(token['access_token']) f.write(token['access_token'])

View file

@ -1,33 +1,35 @@
self: { self: { config
config, , lib
lib, , pkgs
pkgs, , ...
... }:
}:
with lib; with lib;
let let
inherit (pkgs.stdenv.hostPlatform) system; inherit (pkgs.stdenv.hostPlatform) system;
package = self.packages.${system}.default; package = self.packages.${system}.default;
cfg = config.programs.o365-auth; cfg = config.programs.o365-auth;
in { in
{
options.programs.o365-auth = { options.programs.o365-auth = {
enable = mkEnableOption "o365 token refresh script"; enable = mkEnableOption "o365 token refresh script";
package = mkOption {
type = types.package;
default = package;
};
config = mkOption { config = mkOption {
type = types.str; type = types.str;
default = '' default = ''
ClientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584" [mcgill]
ClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82" ClientId = "08162f7c-0fd2-4200-a84a-f25a4db0b584"
Scopes = ['https://outlook.office.com/IMAP.AccessAsUser.All','https://outlook.office.com/SMTP.Send'] ClientSecret = "TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82"
RefreshTokenFileName = "imap_smtp_refresh_token" Scopes = ['https://outlook.office.com/IMAP.AccessAsUser.All','https://outlook.office.com/SMTP.Send']
AccessTokenFileName = "imap_smtp_access_token" Authority = false
'';
Authority = false
'';
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
home.packages = [ package ]; home.packages = [ cfg.package ];
home.file.".o365-auth-config".source = cfg.config; home.file.".o365-auth-config".text = cfg.config;
}; };
} }

View file

@ -3,27 +3,41 @@ from msal import ConfidentialClientApplication, SerializableTokenCache
import config import config
import sys 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 # We use the cache to extract the refresh token
cache = SerializableTokenCache() 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: if 'error' in token:
print(token) print(token)
sys.exit("Failed to get access token") sys.exit("Failed to get access token")
# you're supposed to save the old refresh token each time # 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(cache.find('RefreshToken')[0]['secret'])
f.write(token['refresh_token']) 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']) f.write(token['access_token'])
if print_access_token: if print_access_token:
print(token['access_token']) print(token['access_token'])