microsoft-todo-cli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_todo_cli-1.0.0.dist-info/METADATA +296 -0
- microsoft_todo_cli-1.0.0.dist-info/RECORD +37 -0
- microsoft_todo_cli-1.0.0.dist-info/WHEEL +5 -0
- microsoft_todo_cli-1.0.0.dist-info/entry_points.txt +2 -0
- microsoft_todo_cli-1.0.0.dist-info/licenses/LICENSE +22 -0
- microsoft_todo_cli-1.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/run_tests.py +39 -0
- tests/test_checklist_cli.py +118 -0
- tests/test_checklist_item_model.py +108 -0
- tests/test_checklist_wrapper.py +62 -0
- tests/test_cli_commands.py +436 -0
- tests/test_cli_output.py +169 -0
- tests/test_cli_url_integration.py +136 -0
- tests/test_datetime_parser.py +233 -0
- tests/test_filters.py +120 -0
- tests/test_json_output.py +223 -0
- tests/test_lst_output.py +135 -0
- tests/test_models.py +235 -0
- tests/test_odata_escape.py +175 -0
- tests/test_recurrence.py +159 -0
- tests/test_update_command.py +192 -0
- tests/test_utils.py +186 -0
- tests/test_wrapper.py +191 -0
- todocli/__init__.py +1 -0
- todocli/cli.py +1356 -0
- todocli/graphapi/__init__.py +0 -0
- todocli/graphapi/oauth.py +136 -0
- todocli/graphapi/wrapper.py +660 -0
- todocli/models/__init__.py +0 -0
- todocli/models/checklistitem.py +59 -0
- todocli/models/todolist.py +27 -0
- todocli/models/todotask.py +105 -0
- todocli/utils/__init__.py +0 -0
- todocli/utils/datetime_util.py +321 -0
- todocli/utils/recurrence_util.py +122 -0
- todocli/utils/update_checker.py +55 -0
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Oauth settings
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from requests_oauthlib import OAuth2Session
|
|
9
|
+
|
|
10
|
+
settings = {
|
|
11
|
+
"redirect": "https://localhost/login/authorized",
|
|
12
|
+
"scopes": "openid offline_access tasks.readwrite",
|
|
13
|
+
"authority": "https://login.microsoftonline.com/common",
|
|
14
|
+
"authorize_endpoint": "/oauth2/v2.0/authorize",
|
|
15
|
+
"token_endpoint": "/oauth2/v2.0/token",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Code taken from https://docs.microsoft.com/en-us/graph/tutorials/python?tutorial-step=3
|
|
19
|
+
|
|
20
|
+
# This is necessary because Azure does not guarantee
|
|
21
|
+
# to return scopes in the same case and order as requested
|
|
22
|
+
os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1"
|
|
23
|
+
os.environ["OAUTHLIB_IGNORE_SCOPE_CHANGE"] = "1"
|
|
24
|
+
|
|
25
|
+
redirect = settings["redirect"]
|
|
26
|
+
scope = settings["scopes"]
|
|
27
|
+
|
|
28
|
+
authorize_url = "{0}{1}".format(settings["authority"], settings["authorize_endpoint"])
|
|
29
|
+
token_url = "{0}{1}".format(settings["authority"], settings["token_endpoint"])
|
|
30
|
+
|
|
31
|
+
# User settings location
|
|
32
|
+
config_dir = "{}/.config/tod0".format(os.path.expanduser("~"))
|
|
33
|
+
if not os.path.isdir(config_dir):
|
|
34
|
+
os.makedirs(config_dir)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_keys(keys):
|
|
38
|
+
client_id = keys.get("client_id", "")
|
|
39
|
+
client_secret = keys.get("client_secret", "")
|
|
40
|
+
|
|
41
|
+
if not client_id or not client_secret:
|
|
42
|
+
print(
|
|
43
|
+
"Please enter your client id and secret in {}".format(
|
|
44
|
+
os.path.join(config_dir, "keys.yml")
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
print(
|
|
48
|
+
"Instructions to getting your API client id and secret can be found here:\n{}".format(
|
|
49
|
+
"https://github.com/underwear/microsoft-todo-cli/blob/main/docs/setup-api.md"
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Check for api keys
|
|
56
|
+
keys_path = os.path.join(config_dir, "keys.yml")
|
|
57
|
+
if not os.path.isfile(keys_path):
|
|
58
|
+
keys = {"client_id": "", "client_secret": ""}
|
|
59
|
+
|
|
60
|
+
with open(keys_path, "w") as f:
|
|
61
|
+
yaml.dump(keys, f)
|
|
62
|
+
check_keys(keys)
|
|
63
|
+
else:
|
|
64
|
+
# Load api keys
|
|
65
|
+
with open(keys_path) as f:
|
|
66
|
+
keys = yaml.load(f, yaml.SafeLoader) or {}
|
|
67
|
+
check_keys(keys)
|
|
68
|
+
|
|
69
|
+
client_id = keys["client_id"]
|
|
70
|
+
client_secret = keys["client_secret"]
|
|
71
|
+
|
|
72
|
+
TOKEN_FILE = os.path.join(config_dir, "token.json")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_token():
|
|
76
|
+
token = None
|
|
77
|
+
|
|
78
|
+
# Try to load token from local
|
|
79
|
+
if os.path.isfile(TOKEN_FILE):
|
|
80
|
+
try:
|
|
81
|
+
with open(TOKEN_FILE, "r") as f:
|
|
82
|
+
token = json.load(f)
|
|
83
|
+
token = refresh_token(token)
|
|
84
|
+
except (json.JSONDecodeError, KeyError, OSError):
|
|
85
|
+
token = None
|
|
86
|
+
|
|
87
|
+
if token is None:
|
|
88
|
+
# Authorize user to get token
|
|
89
|
+
outlook = OAuth2Session(client_id, scope=scope, redirect_uri=redirect)
|
|
90
|
+
|
|
91
|
+
# Redirect the user owner to the OAuth provider
|
|
92
|
+
authorization_url, state = outlook.authorization_url(authorize_url)
|
|
93
|
+
print("Please go here and authorize:\n", authorization_url)
|
|
94
|
+
|
|
95
|
+
# Get the authorization verifier code from the callback url
|
|
96
|
+
redirect_response = input("Paste the full redirect URL below:\n")
|
|
97
|
+
|
|
98
|
+
# Fetch the access token
|
|
99
|
+
token = outlook.fetch_token(
|
|
100
|
+
token_url,
|
|
101
|
+
client_secret=client_secret,
|
|
102
|
+
authorization_response=redirect_response,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
store_token(token)
|
|
106
|
+
return token
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def store_token(token):
|
|
110
|
+
with open(TOKEN_FILE, "w") as f:
|
|
111
|
+
json.dump(token, f)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def refresh_token(token):
|
|
115
|
+
# Check expiration
|
|
116
|
+
now = time.time()
|
|
117
|
+
# Subtract 5 minutes from expiration to account for clock skew
|
|
118
|
+
expire_time = token["expires_at"] - 300
|
|
119
|
+
if now >= expire_time:
|
|
120
|
+
# Refresh the token
|
|
121
|
+
aad_auth = OAuth2Session(
|
|
122
|
+
client_id, token=token, scope=scope, redirect_uri=redirect
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
refresh_params = {"client_id": client_id, "client_secret": client_secret}
|
|
126
|
+
|
|
127
|
+
new_token = aad_auth.refresh_token(token_url, **refresh_params)
|
|
128
|
+
return new_token
|
|
129
|
+
|
|
130
|
+
# Token still valid, just return it
|
|
131
|
+
return token
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_oauth_session():
|
|
135
|
+
token = get_token()
|
|
136
|
+
return OAuth2Session(client_id, scope=scope, token=token)
|