Skip to content

Commit 60cb484

Browse files
committed
Add support for fine-grained tokens
1 parent 07e32b1 commit 60cb484

File tree

1 file changed

+39
-20
lines changed

1 file changed

+39
-20
lines changed

github_backup/github_backup.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,13 @@ def parse_args(args=None):
150150
'If a username is given but not a password, the '
151151
'password will be prompted for.')
152152
parser.add_argument('-t',
153-
'--token',
154-
dest='token',
153+
'--token-classic',
154+
dest='token_classic',
155155
help='personal access, OAuth, or JSON Web token, or path to token (file://...)') # noqa
156+
parser.add_argument('-f',
157+
'--token-fine',
158+
dest='token_fine',
159+
help='fine-grained personal access token (github_pat_....)') # noqa
156160
parser.add_argument('--as-app',
157161
action='store_true',
158162
dest='as_app',
@@ -357,18 +361,23 @@ def get_auth(args, encode=True, for_git_cli=False):
357361
raise Exception('No password item matching the provided name and account could be found in the osx keychain.')
358362
elif args.osx_keychain_item_account:
359363
raise Exception('You must specify both name and account fields for osx keychain password items')
360-
elif args.token:
364+
elif args.token_fine:
365+
if args.token_fine.startswith("github_pat_"):
366+
auth = args.token_fine
367+
else:
368+
raise Exception("Fine-grained token supplied does not look like a GitHub PAT")
369+
elif args.token_classic:
361370
_path_specifier = 'file://'
362-
if args.token.startswith(_path_specifier):
363-
args.token = open(args.token[len(_path_specifier):],
364-
'rt').readline().strip()
371+
if args.token_classic.startswith(_path_specifier):
372+
args.token_classic = open(args.token_classic[len(_path_specifier):],
373+
'rt').readline().strip()
365374
if not args.as_app:
366-
auth = args.token + ':' + 'x-oauth-basic'
375+
auth = args.token_classic + ':' + 'x-oauth-basic'
367376
else:
368377
if not for_git_cli:
369-
auth = args.token
378+
auth = args.token_classic
370379
else:
371-
auth = 'x-access-token:' + args.token
380+
auth = 'x-access-token:' + args.token_classic
372381
elif args.username:
373382
if not args.password:
374383
args.password = getpass.getpass()
@@ -383,7 +392,7 @@ def get_auth(args, encode=True, for_git_cli=False):
383392
if not auth:
384393
return None
385394

386-
if not encode:
395+
if not encode or args.token_fine is not None:
387396
return auth
388397

389398
return base64.b64encode(auth.encode('ascii'))
@@ -421,12 +430,19 @@ def get_github_repo_url(args, repository):
421430
return repository['ssh_url']
422431

423432
auth = get_auth(args, encode=False, for_git_cli=True)
424-
if auth:
425-
repo_url = 'https://{0}@{1}/{2}/{3}.git'.format(
426-
auth,
427-
get_github_host(args),
428-
repository['owner']['login'],
429-
repository['name'])
433+
if auth:
434+
if args.token_fine is None:
435+
repo_url = 'https://{0}@{1}/{2}/{3}.git'.format(
436+
auth,
437+
get_github_host(args),
438+
repository['owner']['login'],
439+
repository['name'])
440+
else:
441+
repo_url = 'https://{0}@{1}/{2}/{3}.git'.format(
442+
"oauth2:"+auth,
443+
get_github_host(args),
444+
repository['owner']['login'],
445+
repository['name'])
430446
else:
431447
repo_url = repository['clone_url']
432448

@@ -441,7 +457,7 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
441457

442458
while True:
443459
page = page + 1
444-
request = _construct_request(per_page, page, query_args, template, auth, as_app=args.as_app) # noqa
460+
request = _construct_request(per_page, page, query_args, template, auth, as_app=args.as_app, fine=True if args.token_fine is not None else False) # noqa
445461
r, errors = _get_response(request, auth, template)
446462

447463
status_code = int(r.getcode())
@@ -474,7 +490,7 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
474490
log_warning('API request failed. Retrying in 5 seconds')
475491
retries += 1
476492
time.sleep(5)
477-
request = _construct_request(per_page, page, query_args, template, auth, as_app=args.as_app) # noqa
493+
request = _construct_request(per_page, page, query_args, template, auth, as_app=args.as_app, fine=True if args.token_fine is not None else False) # noqa
478494
r, errors = _get_response(request, auth, template)
479495

480496
status_code = int(r.getcode())
@@ -557,7 +573,7 @@ def _get_response(request, auth, template):
557573
return r, errors
558574

559575

560-
def _construct_request(per_page, page, query_args, template, auth, as_app=None):
576+
def _construct_request(per_page, page, query_args, template, auth, as_app=None, fine=False):
561577
querystring = urlencode(dict(list({
562578
'per_page': per_page,
563579
'page': page
@@ -566,7 +582,10 @@ def _construct_request(per_page, page, query_args, template, auth, as_app=None):
566582
request = Request(template + '?' + querystring)
567583
if auth is not None:
568584
if not as_app:
569-
request.add_header('Authorization', 'Basic '.encode('ascii') + auth)
585+
if fine:
586+
request.add_header('Authorization', 'token ' + auth)
587+
else:
588+
request.add_header('Authorization', 'Basic '.encode('ascii') + auth)
570589
else:
571590
auth = auth.encode('ascii')
572591
request.add_header('Authorization', 'token '.encode('ascii') + auth)

0 commit comments

Comments
 (0)