mirror of
https://github.com/vale981/outlook-oauth-hack
synced 2025-03-04 08:31:38 -05:00
implement profiles
This commit is contained in:
parent
c9bdbb305c
commit
f85a1900b7
6 changed files with 119 additions and 132 deletions
28
config.py
28
config.py
|
@ -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
87
flake.lock
generated
|
@ -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",
|
||||||
|
|
52
flake.nix
52
flake.nix
|
@ -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;
|
};
|
||||||
});
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
22
get_token.py
22
get_token.py
|
@ -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'])
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'])
|
||||||
|
|
Loading…
Add table
Reference in a new issue