tccli 3.0.1190.1__py2.py3-none-any.whl → 3.0.1192.1__py2.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.
- tccli/__init__.py +1 -1
- tccli/command.py +7 -1
- tccli/configure.py +11 -3
- tccli/loaders.py +43 -8
- tccli/oauth.py +115 -0
- tccli/plugin.py +30 -0
- tccli/plugins/__init__.py +0 -0
- tccli/plugins/auth/__init__.py +59 -0
- tccli/plugins/auth/browser_flow.py +88 -0
- tccli/plugins/auth/login.py +130 -0
- tccli/plugins/auth/logout.py +21 -0
- tccli/plugins/auth/texts.py +30 -0
- tccli/plugins/test/__init__.py +87 -0
- tccli/plugins/test/add.py +31 -0
- tccli/services/apm/v20210622/api.json +19 -0
- tccli/services/cam/cam_client.py +138 -32
- tccli/services/cam/v20190116/api.json +203 -0
- tccli/services/cam/v20190116/examples.json +16 -0
- tccli/services/cdb/v20170320/api.json +13 -4
- tccli/services/cfw/cfw_client.py +53 -0
- tccli/services/cfw/v20190904/api.json +473 -0
- tccli/services/cfw/v20190904/examples.json +8 -0
- tccli/services/ckafka/ckafka_client.py +53 -0
- tccli/services/ckafka/v20190819/api.json +80 -0
- tccli/services/ckafka/v20190819/examples.json +8 -0
- tccli/services/cls/v20201016/examples.json +1 -1
- tccli/services/dasb/dasb_client.py +53 -0
- tccli/services/dasb/v20191018/api.json +168 -0
- tccli/services/dasb/v20191018/examples.json +8 -0
- tccli/services/dlc/v20210125/api.json +298 -45
- tccli/services/dlc/v20210125/examples.json +21 -21
- tccli/services/dsgc/v20190723/api.json +13 -3
- tccli/services/emr/v20190103/api.json +8 -8
- tccli/services/ess/v20201111/api.json +49 -16
- tccli/services/ess/v20201111/examples.json +7 -1
- tccli/services/essbasic/v20210526/api.json +28 -1
- tccli/services/essbasic/v20210526/examples.json +43 -1
- tccli/services/irp/v20220805/api.json +11 -2
- tccli/services/lke/v20231130/api.json +82 -4
- tccli/services/mps/v20190612/api.json +126 -5
- tccli/services/ocr/v20181119/api.json +10 -0
- tccli/services/ocr/v20181119/examples.json +1 -1
- tccli/services/omics/v20221128/api.json +13 -2
- tccli/services/region/v20220627/api.json +11 -11
- tccli/services/region/v20220627/examples.json +1 -1
- tccli/services/tat/v20201028/api.json +10 -0
- tccli/services/tcb/v20180608/api.json +2 -2
- tccli/services/tdmq/v20200217/api.json +2 -2
- tccli/services/tdmq/v20200217/examples.json +2 -2
- tccli/services/tmt/v20180321/api.json +2 -2
- tccli/services/vdb/v20230616/api.json +38 -1
- tccli/services/vdb/v20230616/examples.json +2 -2
- tccli/services/vod/v20180717/api.json +30 -9
- tccli/services/vod/v20180717/examples.json +1 -1
- tccli/services/vpc/v20170312/api.json +3 -3
- tccli/services/wedata/v20210820/api.json +1 -1
- {tccli-3.0.1190.1.dist-info → tccli-3.0.1192.1.dist-info}/METADATA +2 -2
- {tccli-3.0.1190.1.dist-info → tccli-3.0.1192.1.dist-info}/RECORD +61 -51
- {tccli-3.0.1190.1.dist-info → tccli-3.0.1192.1.dist-info}/WHEEL +0 -0
- {tccli-3.0.1190.1.dist-info → tccli-3.0.1192.1.dist-info}/entry_points.txt +0 -0
- {tccli-3.0.1190.1.dist-info → tccli-3.0.1192.1.dist-info}/license_files/LICENSE +0 -0
tccli/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '3.0.
|
1
|
+
__version__ = '3.0.1192.1'
|
tccli/command.py
CHANGED
@@ -6,6 +6,8 @@ import copy
|
|
6
6
|
import tccli.services as Services
|
7
7
|
import tccli.options_define as Options_define
|
8
8
|
from collections import OrderedDict
|
9
|
+
|
10
|
+
from tccli import oauth
|
9
11
|
from tccli.utils import Utils
|
10
12
|
from tccli.argument import CLIArgument, CustomArgument, ListArgument, BooleanArgument
|
11
13
|
from tccli.exceptions import UnknownArgumentError
|
@@ -176,12 +178,15 @@ class ServiceCommand(BaseCommand):
|
|
176
178
|
service_model = self._get_service_model()
|
177
179
|
for action in service_model["actions"]:
|
178
180
|
action_model = service_model["actions"][action]
|
181
|
+
action_caller = action_model.get("action_caller", None)
|
182
|
+
if not action_caller:
|
183
|
+
action_caller = Services.action_caller(self._service_name)()[action]
|
179
184
|
command_map[action] = ActionCommand(
|
180
185
|
service_name=self._service_name,
|
181
186
|
version=self._version,
|
182
187
|
action_name=action,
|
183
188
|
action_model=action_model,
|
184
|
-
action_caller=
|
189
|
+
action_caller=action_caller,
|
185
190
|
)
|
186
191
|
return command_map
|
187
192
|
|
@@ -286,6 +291,7 @@ class ActionCommand(BaseCommand):
|
|
286
291
|
action_parameters = self.cli_unfold_argument.build_action_parameters(parsed_args)
|
287
292
|
else:
|
288
293
|
action_parameters = self._build_action_parameters(parsed_args, self.argument_map)
|
294
|
+
oauth.maybe_refresh_credential(parsed_globals.profile if parsed_globals.profile else "default")
|
289
295
|
return self._action_caller(action_parameters, vars(parsed_globals))
|
290
296
|
|
291
297
|
def create_help_command(self):
|
tccli/configure.py
CHANGED
@@ -416,16 +416,24 @@ class ConfigureCommand(BasicConfigure):
|
|
416
416
|
|
417
417
|
def init_configures(self):
|
418
418
|
config = {}
|
419
|
-
|
419
|
+
import argparse
|
420
|
+
parser = argparse.ArgumentParser()
|
421
|
+
parser.add_argument("--profile", type=str)
|
422
|
+
args, _ = parser.parse_known_args()
|
423
|
+
profile = args.profile or "default"
|
424
|
+
profile_file = "%s.configure" % profile
|
425
|
+
|
426
|
+
if not self._profile_existed(profile_file)[0]:
|
420
427
|
config = {
|
421
428
|
"region": "ap-guangzhou",
|
422
429
|
"output": "json",
|
423
430
|
"arrayCount": 10,
|
424
431
|
"warning": "off"
|
425
432
|
}
|
426
|
-
self._init_configure(
|
433
|
+
self._init_configure(profile_file, config)
|
434
|
+
|
427
435
|
for profile_name in os.listdir(self.cli_path):
|
428
|
-
if profile_name ==
|
436
|
+
if profile_name == profile_file:
|
429
437
|
continue
|
430
438
|
if profile_name.endswith(".configure"):
|
431
439
|
self._init_configure(profile_name, {})
|
tccli/loaders.py
CHANGED
@@ -9,6 +9,7 @@ from tccli.utils import Utils
|
|
9
9
|
from tccli import __version__
|
10
10
|
from tccli.services import SERVICE_VERSIONS
|
11
11
|
from collections import OrderedDict
|
12
|
+
import tccli.plugin as plugin
|
12
13
|
|
13
14
|
BASE_TYPE = ["int64", "uint64", "string", "float", "bool", "date", "datetime", "datetime_iso", "binary"]
|
14
15
|
CLI_BASE_TYPE = ["Integer", "String", "Float", "Timestamp", "Boolean", "Binary"]
|
@@ -175,7 +176,15 @@ class Loader(object):
|
|
175
176
|
return version[1:5] + "-" + version[5:7] + "-" + version[7:9]
|
176
177
|
|
177
178
|
def get_available_services(self):
|
178
|
-
|
179
|
+
services = copy.deepcopy(SERVICE_VERSIONS)
|
180
|
+
for name, vers in plugin.import_plugins().items():
|
181
|
+
if name not in services:
|
182
|
+
services[name] = []
|
183
|
+
for ver, spec in vers.items():
|
184
|
+
api_ver = spec["metadata"]["apiVersion"]
|
185
|
+
if api_ver not in services[name]:
|
186
|
+
services[name].append(api_ver)
|
187
|
+
return services
|
179
188
|
|
180
189
|
def get_service_default_version(self, service):
|
181
190
|
args = sys.argv[1:]
|
@@ -194,15 +203,41 @@ class Loader(object):
|
|
194
203
|
services_path = self.get_services_path()
|
195
204
|
version = "v" + version.replace('-', '')
|
196
205
|
apis_path = os.path.join(services_path, service, version, "api.json")
|
197
|
-
|
206
|
+
model = {
|
207
|
+
"metadata": {},
|
208
|
+
"actions": {},
|
209
|
+
"objects": {},
|
210
|
+
}
|
211
|
+
if os.path.exists(apis_path):
|
212
|
+
if six.PY2:
|
213
|
+
with open(apis_path, 'r') as f:
|
214
|
+
model = json.load(f)
|
215
|
+
else:
|
216
|
+
with open(apis_path, 'r', encoding='utf-8') as f:
|
217
|
+
model = json.load(f)
|
218
|
+
|
219
|
+
# merge plugins
|
220
|
+
for plugin_name, vers in plugin.import_plugins().items():
|
221
|
+
|
222
|
+
if plugin_name != service:
|
223
|
+
continue
|
224
|
+
|
225
|
+
for ver, spec in vers.items():
|
226
|
+
|
227
|
+
# 2017-03-12 -> v20170312
|
228
|
+
compact_ver = 'v' + ver.replace('-', '')
|
229
|
+
|
230
|
+
if compact_ver != version:
|
231
|
+
continue
|
232
|
+
|
233
|
+
model["metadata"].update(spec["metadata"])
|
234
|
+
model["actions"].update(spec["actions"])
|
235
|
+
model["objects"].update(spec["objects"])
|
236
|
+
|
237
|
+
if not model:
|
198
238
|
raise Exception("Not find service:%s version:%s model" % (service, version))
|
199
239
|
|
200
|
-
|
201
|
-
with open(apis_path, 'r') as f:
|
202
|
-
return json.load(f)
|
203
|
-
else:
|
204
|
-
with open(apis_path, 'r', encoding='utf-8') as f:
|
205
|
-
return json.load(f)
|
240
|
+
return model
|
206
241
|
|
207
242
|
def get_service_description(self, service, version):
|
208
243
|
service_model = self.get_service_model(service, version)
|
tccli/oauth.py
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
|
5
|
+
import requests
|
6
|
+
import uuid
|
7
|
+
|
8
|
+
_API_ENDPOINT = "https://cli.cloud.tencent.com"
|
9
|
+
_CRED_REFRESH_SAFE_DUR = 60 * 5
|
10
|
+
_ACCESS_REFRESH_SAFE_DUR = 60 * 5
|
11
|
+
|
12
|
+
|
13
|
+
def maybe_refresh_credential(profile):
|
14
|
+
cred_path = cred_path_of_profile(profile)
|
15
|
+
try:
|
16
|
+
with open(cred_path, "r") as cred_file:
|
17
|
+
cred = json.load(cred_file)
|
18
|
+
except IOError:
|
19
|
+
# file not found, don't check
|
20
|
+
return
|
21
|
+
|
22
|
+
if cred.get("type") != "oauth":
|
23
|
+
return
|
24
|
+
|
25
|
+
try:
|
26
|
+
now = time.time()
|
27
|
+
|
28
|
+
expires_at = cred["expiresAt"]
|
29
|
+
if expires_at - now > _CRED_REFRESH_SAFE_DUR:
|
30
|
+
return
|
31
|
+
|
32
|
+
token_info = cred["oauth"]
|
33
|
+
site = token_info["site"]
|
34
|
+
access_expires = token_info["expiresAt"]
|
35
|
+
if access_expires - now < _ACCESS_REFRESH_SAFE_DUR:
|
36
|
+
refresh_token = token_info["refreshToken"]
|
37
|
+
open_id = token_info["openId"]
|
38
|
+
new_token = refresh_user_token(refresh_token, open_id, site)
|
39
|
+
token_info.update(new_token)
|
40
|
+
|
41
|
+
access_token = token_info["accessToken"]
|
42
|
+
new_cred = get_temp_cred(access_token, site)
|
43
|
+
save_credential(token_info, new_cred, profile)
|
44
|
+
|
45
|
+
except KeyError as e:
|
46
|
+
print("failed to refresh credential, your credential file(%s) is corrupted, %s" % (cred_path, e))
|
47
|
+
|
48
|
+
except Exception as e:
|
49
|
+
print("failed to refresh credential, %s" % e)
|
50
|
+
|
51
|
+
|
52
|
+
def refresh_user_token(ref_token, open_id, site):
|
53
|
+
api_endpoint = _API_ENDPOINT + "/refresh_user_token"
|
54
|
+
body = {
|
55
|
+
"TraceId": str(uuid.uuid4()),
|
56
|
+
"RefreshToken": ref_token,
|
57
|
+
"OpenId": open_id,
|
58
|
+
"Site": site,
|
59
|
+
}
|
60
|
+
http_response = requests.post(api_endpoint, json=body, verify=False)
|
61
|
+
resp = http_response.json()
|
62
|
+
|
63
|
+
if "Error" in resp:
|
64
|
+
raise ValueError("refresh_user_token: %s" % json.dumps(resp))
|
65
|
+
|
66
|
+
return {
|
67
|
+
"accessToken": resp["AccessToken"],
|
68
|
+
"expiresAt": resp["ExpiresAt"],
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
def get_temp_cred(access_token, site):
|
73
|
+
api_endpoint = _API_ENDPOINT + "/get_temp_cred"
|
74
|
+
body = {
|
75
|
+
"TraceId": str(uuid.uuid4()),
|
76
|
+
"AccessToken": access_token,
|
77
|
+
"Site": site,
|
78
|
+
}
|
79
|
+
http_response = requests.post(api_endpoint, json=body, verify=False)
|
80
|
+
resp = http_response.json()
|
81
|
+
|
82
|
+
if "Error" in resp:
|
83
|
+
raise ValueError("get_temp_key: %s" % json.dumps(resp))
|
84
|
+
|
85
|
+
return {
|
86
|
+
"secretId": resp["SecretId"],
|
87
|
+
"secretKey": resp["SecretKey"],
|
88
|
+
"token": resp["Token"],
|
89
|
+
"expiresAt": resp["ExpiresAt"],
|
90
|
+
}
|
91
|
+
|
92
|
+
|
93
|
+
def cred_path_of_profile(profile):
|
94
|
+
return os.path.join(os.path.expanduser("~"), ".tccli", profile + ".credential")
|
95
|
+
|
96
|
+
|
97
|
+
def save_credential(token, new_cred, profile):
|
98
|
+
cred_path = cred_path_of_profile(profile)
|
99
|
+
|
100
|
+
cred = {
|
101
|
+
"type": "oauth",
|
102
|
+
"secretId": new_cred["secretId"],
|
103
|
+
"secretKey": new_cred["secretKey"],
|
104
|
+
"token": new_cred["token"],
|
105
|
+
"expiresAt": new_cred["expiresAt"],
|
106
|
+
"oauth": {
|
107
|
+
"openId": token["openId"],
|
108
|
+
"accessToken": token["accessToken"],
|
109
|
+
"expiresAt": token["expiresAt"],
|
110
|
+
"refreshToken": token["refreshToken"],
|
111
|
+
"site": token["site"],
|
112
|
+
},
|
113
|
+
}
|
114
|
+
with open(cred_path, "w") as cred_file:
|
115
|
+
json.dump(cred, cred_file, indent=4)
|
tccli/plugin.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import importlib
|
2
|
+
import logging
|
3
|
+
import pkgutil
|
4
|
+
|
5
|
+
import tccli.plugins as plugins
|
6
|
+
|
7
|
+
_plugins = {}
|
8
|
+
_imported = False
|
9
|
+
|
10
|
+
|
11
|
+
def import_plugins():
|
12
|
+
global _imported
|
13
|
+
|
14
|
+
if not _imported:
|
15
|
+
_reimport_plugins()
|
16
|
+
_imported = True
|
17
|
+
|
18
|
+
return _plugins
|
19
|
+
|
20
|
+
|
21
|
+
def _reimport_plugins():
|
22
|
+
for _, name, _ in pkgutil.iter_modules(plugins.__path__, plugins.__name__ + "."):
|
23
|
+
mod = importlib.import_module(name)
|
24
|
+
register_service = getattr(mod, "register_service", None)
|
25
|
+
if not register_service:
|
26
|
+
logging.warning("invalid module %s" % name)
|
27
|
+
continue
|
28
|
+
register_service(_plugins)
|
29
|
+
|
30
|
+
return _plugins
|
File without changes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
from tccli.plugins.auth.login import login_command_entrypoint
|
3
|
+
from tccli.plugins.auth.logout import logout_command_entrypoint
|
4
|
+
|
5
|
+
service_name = "auth"
|
6
|
+
service_version = "2024-08-20"
|
7
|
+
|
8
|
+
_spec = {
|
9
|
+
"metadata": {
|
10
|
+
"serviceShortName": service_name,
|
11
|
+
"apiVersion": service_version,
|
12
|
+
"description": "auth related commands",
|
13
|
+
},
|
14
|
+
"actions": {
|
15
|
+
"login": {
|
16
|
+
"name": "登陆",
|
17
|
+
"document": "login through sso",
|
18
|
+
"input": "loginRequest",
|
19
|
+
"output": "loginResponse",
|
20
|
+
"action_caller": login_command_entrypoint,
|
21
|
+
},
|
22
|
+
"logout": {
|
23
|
+
"name": "登出",
|
24
|
+
"document": "remove local credential file",
|
25
|
+
"input": "logoutRequest",
|
26
|
+
"output": "logoutResponse",
|
27
|
+
"action_caller": logout_command_entrypoint,
|
28
|
+
},
|
29
|
+
},
|
30
|
+
"objects": {
|
31
|
+
"loginRequest": {
|
32
|
+
"members": [
|
33
|
+
{
|
34
|
+
"name": "browser",
|
35
|
+
"member": "string",
|
36
|
+
"type": "string",
|
37
|
+
"required": False,
|
38
|
+
"document": "use browser=no to indicate no browser login mode",
|
39
|
+
},
|
40
|
+
],
|
41
|
+
},
|
42
|
+
"loginResponse": {
|
43
|
+
"members": [],
|
44
|
+
},
|
45
|
+
"logoutRequest": {
|
46
|
+
"members": [],
|
47
|
+
},
|
48
|
+
"logoutResponse": {
|
49
|
+
"members": [],
|
50
|
+
},
|
51
|
+
},
|
52
|
+
"version": "1.0",
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
def register_service(specs):
|
57
|
+
specs[service_name] = {
|
58
|
+
service_version: _spec,
|
59
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import time
|
2
|
+
import traceback
|
3
|
+
import socket
|
4
|
+
from threading import Thread
|
5
|
+
from tccli import oauth
|
6
|
+
|
7
|
+
try:
|
8
|
+
from urlparse import urlparse, parse_qs
|
9
|
+
from Queue import Queue
|
10
|
+
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
11
|
+
from SocketServer import ThreadingTCPServer, TCPServer
|
12
|
+
except ImportError:
|
13
|
+
from urllib.parse import urlparse, parse_qs
|
14
|
+
from queue import Queue
|
15
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
16
|
+
from socketserver import ThreadingTCPServer, TCPServer
|
17
|
+
|
18
|
+
|
19
|
+
# chrome keeps previous connection alive, so use threading to avoid blocking
|
20
|
+
class ThreadingHTTPServer(ThreadingTCPServer):
|
21
|
+
allow_reuse_address = 1
|
22
|
+
|
23
|
+
def server_bind(self):
|
24
|
+
"""Override server_bind to store the server name."""
|
25
|
+
TCPServer.server_bind(self)
|
26
|
+
host, port = self.socket.getsockname()[:2]
|
27
|
+
self.server_name = socket.getfqdn(host)
|
28
|
+
self.server_port = port
|
29
|
+
|
30
|
+
|
31
|
+
class HTTPHandler(BaseHTTPRequestHandler):
|
32
|
+
result_queue = Queue(1)
|
33
|
+
|
34
|
+
def do_GET(self):
|
35
|
+
try:
|
36
|
+
parsed_url = urlparse(self.path)
|
37
|
+
query_vals = parse_qs(parsed_url.query)
|
38
|
+
|
39
|
+
open_id = query_vals.get("open_id")[0]
|
40
|
+
access_token = query_vals.get("access_token")[0]
|
41
|
+
refresh_token = query_vals.get("refresh_token")[0]
|
42
|
+
expires_at = int(query_vals.get("expires_at")[0])
|
43
|
+
state = query_vals.get("state")[0]
|
44
|
+
redirect_url = query_vals.get("redirect_url")[0]
|
45
|
+
site = query_vals.get("site")[0]
|
46
|
+
token = {
|
47
|
+
"openId": open_id,
|
48
|
+
"accessToken": access_token,
|
49
|
+
"refreshToken": refresh_token,
|
50
|
+
"expiresAt": expires_at,
|
51
|
+
"state": state,
|
52
|
+
"site": site,
|
53
|
+
}
|
54
|
+
cred = oauth.get_temp_cred(token["accessToken"], token["site"])
|
55
|
+
self.result_queue.put((token, cred))
|
56
|
+
self.send_response(307)
|
57
|
+
self.send_header("Location", redirect_url)
|
58
|
+
self.end_headers()
|
59
|
+
except Exception:
|
60
|
+
err = traceback.format_exc()
|
61
|
+
print(err)
|
62
|
+
self.send_response(400)
|
63
|
+
self.end_headers()
|
64
|
+
self.wfile.write("login failed due to the following error:\n\n".encode("utf-8"))
|
65
|
+
self.wfile.write(err.encode("utf-8"))
|
66
|
+
self.wfile.flush()
|
67
|
+
|
68
|
+
# suppress debug message
|
69
|
+
def log_message(self, format, *args):
|
70
|
+
pass
|
71
|
+
|
72
|
+
|
73
|
+
def try_run(start_search_port, end_search_port):
|
74
|
+
port = start_search_port
|
75
|
+
|
76
|
+
while port <= end_search_port:
|
77
|
+
server_address = ('', port)
|
78
|
+
try:
|
79
|
+
ThreadingHTTPServer.daemon_threads = True
|
80
|
+
httpd = ThreadingHTTPServer(server_address, HTTPHandler)
|
81
|
+
t = Thread(target=httpd.serve_forever)
|
82
|
+
t.setDaemon(True)
|
83
|
+
t.start()
|
84
|
+
return port, HTTPHandler.result_queue
|
85
|
+
except socket.error:
|
86
|
+
port += 1
|
87
|
+
|
88
|
+
raise socket.error("no port available from range [%d, %d]" % (start_search_port, end_search_port))
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
import base64
|
3
|
+
import json
|
4
|
+
import random
|
5
|
+
import string
|
6
|
+
import sys
|
7
|
+
import time
|
8
|
+
from six.moves.urllib.parse import urlencode
|
9
|
+
import webbrowser
|
10
|
+
|
11
|
+
from tccli import oauth
|
12
|
+
from tccli.plugins.auth import texts
|
13
|
+
|
14
|
+
_APP_ID = 100038427476
|
15
|
+
_AUTH_URL = "https://cloud.tencent.com/open/authorize"
|
16
|
+
_REDIRECT_URL = "https://cli.cloud.tencent.com/oauth"
|
17
|
+
_SITE = "cn"
|
18
|
+
_DEFAULT_LANG = "zh-CN"
|
19
|
+
|
20
|
+
_START_SEARCH_PORT = 9000
|
21
|
+
_END_SEARCH_PORT = _START_SEARCH_PORT + 100
|
22
|
+
|
23
|
+
|
24
|
+
def print_message(msg):
|
25
|
+
print(msg)
|
26
|
+
sys.stdout.flush()
|
27
|
+
|
28
|
+
|
29
|
+
def login_command_entrypoint(args, parsed_globals):
|
30
|
+
language = parsed_globals.get("language")
|
31
|
+
if not language:
|
32
|
+
language = _DEFAULT_LANG
|
33
|
+
texts.set_lang(language)
|
34
|
+
|
35
|
+
profile = parsed_globals.get("profile", "default")
|
36
|
+
if not profile:
|
37
|
+
profile = "default"
|
38
|
+
|
39
|
+
browser = args.get("browser")
|
40
|
+
|
41
|
+
login(browser != "no", profile, language)
|
42
|
+
|
43
|
+
|
44
|
+
def login(use_browser, profile, language):
|
45
|
+
characters = string.ascii_letters + string.digits
|
46
|
+
state = ''.join(random.choice(characters) for _ in range(10))
|
47
|
+
|
48
|
+
if use_browser:
|
49
|
+
token, cred = _get_token(state, language)
|
50
|
+
else:
|
51
|
+
token, cred = _get_token_no_browser(state, language)
|
52
|
+
|
53
|
+
if token["state"] != state:
|
54
|
+
raise ValueError("invalid state %s" % token["state"])
|
55
|
+
|
56
|
+
oauth.save_credential(token, cred, profile)
|
57
|
+
|
58
|
+
print_message("")
|
59
|
+
print_message(texts.get("login_success") % oauth.cred_path_of_profile(profile))
|
60
|
+
|
61
|
+
|
62
|
+
def _get_token(state, language):
|
63
|
+
from tccli.plugins.auth import browser_flow
|
64
|
+
|
65
|
+
port, result_queue = browser_flow.try_run(_START_SEARCH_PORT, _END_SEARCH_PORT)
|
66
|
+
|
67
|
+
redirect_params = {
|
68
|
+
"redirect_url": "http://localhost:%d" % port,
|
69
|
+
"lang": language,
|
70
|
+
"site": _SITE,
|
71
|
+
}
|
72
|
+
redirect_query = urlencode(redirect_params)
|
73
|
+
redirect_url = _REDIRECT_URL + "?" + redirect_query
|
74
|
+
url_params = {
|
75
|
+
"scope": "login",
|
76
|
+
"app_id": _APP_ID,
|
77
|
+
"redirect_url": redirect_url,
|
78
|
+
"state": state,
|
79
|
+
}
|
80
|
+
url_query = urlencode(url_params)
|
81
|
+
auth_url = _AUTH_URL + "?" + url_query
|
82
|
+
|
83
|
+
if not webbrowser.open(auth_url):
|
84
|
+
print_message(texts.get("login_failed_due_to_no_browser"))
|
85
|
+
sys.exit(1)
|
86
|
+
|
87
|
+
print_message(texts.get("login_prompt"))
|
88
|
+
print_message(auth_url)
|
89
|
+
|
90
|
+
# use polling to avoid being unresponsive in python2
|
91
|
+
while result_queue.empty():
|
92
|
+
time.sleep(1)
|
93
|
+
|
94
|
+
result = result_queue.get()
|
95
|
+
if isinstance(result, Exception):
|
96
|
+
raise result
|
97
|
+
|
98
|
+
return result
|
99
|
+
|
100
|
+
|
101
|
+
def _get_token_no_browser(state, language):
|
102
|
+
redirect_params = {
|
103
|
+
"browser": "no",
|
104
|
+
"lang": language,
|
105
|
+
"site": _SITE,
|
106
|
+
}
|
107
|
+
redirect_query = urlencode(redirect_params)
|
108
|
+
redirect_url = _REDIRECT_URL + "?" + redirect_query
|
109
|
+
url_params = {
|
110
|
+
"scope": "login",
|
111
|
+
"app_id": _APP_ID,
|
112
|
+
"redirect_url": redirect_url,
|
113
|
+
"state": state,
|
114
|
+
}
|
115
|
+
url_query = urlencode(url_params)
|
116
|
+
auth_url = _AUTH_URL + "?" + url_query
|
117
|
+
|
118
|
+
print_message(texts.get("login_prompt_no_browser"))
|
119
|
+
print_message("")
|
120
|
+
print_message(auth_url)
|
121
|
+
|
122
|
+
try:
|
123
|
+
input_func = raw_input
|
124
|
+
except NameError:
|
125
|
+
input_func = input
|
126
|
+
|
127
|
+
user_input = input_func(texts.get("login_prompt_code_no_browser"))
|
128
|
+
token = json.loads(base64.b64decode(user_input))
|
129
|
+
cred = oauth.get_temp_cred(token["accessToken"], token["site"])
|
130
|
+
return token, cred
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
import os
|
3
|
+
|
4
|
+
from tccli import oauth
|
5
|
+
from tccli.plugins.auth import texts
|
6
|
+
|
7
|
+
|
8
|
+
def logout_command_entrypoint(args, parsed_globals):
|
9
|
+
language = parsed_globals.get("language")
|
10
|
+
if not language:
|
11
|
+
language = "zh-CN"
|
12
|
+
texts.set_lang(language)
|
13
|
+
|
14
|
+
profile = parsed_globals.get("profile", "default")
|
15
|
+
if not profile:
|
16
|
+
profile = "default"
|
17
|
+
|
18
|
+
cred_path = oauth.cred_path_of_profile(profile)
|
19
|
+
if os.path.exists(cred_path):
|
20
|
+
os.remove(cred_path)
|
21
|
+
print(texts.get("logout") % cred_path)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
_lang = "zh-CN"
|
3
|
+
|
4
|
+
texts = {
|
5
|
+
"zh-CN": {
|
6
|
+
"login_prompt": "您的浏览器已打开, 请根据提示完成登录",
|
7
|
+
"login_prompt_no_browser": "在浏览器中转到以下链接, 并根据提示完成登录:",
|
8
|
+
"login_prompt_code_no_browser": "完成后,输入浏览器中提供的验证码:",
|
9
|
+
"login_failed_due_to_no_browser": "无法打开浏览器, 请尝试添加 '--browser no' 选项",
|
10
|
+
"login_success": "登陆成功, 密钥凭证已被写入: %s",
|
11
|
+
"logout": "登出成功, 密钥凭证已被删除: %s",
|
12
|
+
},
|
13
|
+
"en-US": {
|
14
|
+
"login_prompt": "Your browser is open, please complete the login according to the prompts",
|
15
|
+
"login_prompt_no_browser": "Go to the following link in your browser, and complete the sign-in prompts:",
|
16
|
+
"login_prompt_code_no_browser": "Once finished, enter the verification code provided in your browser:",
|
17
|
+
"login_failed_due_to_no_browser": "Failed to launch browser, please try option '--browser no'",
|
18
|
+
"login_success": "Login succeed, credential has been written to %s",
|
19
|
+
"logout": "Logout succeed, credential has been removed: %s",
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
def set_lang(lang):
|
25
|
+
global _lang
|
26
|
+
_lang = lang
|
27
|
+
|
28
|
+
|
29
|
+
def get(key):
|
30
|
+
return texts[_lang][key]
|