Skip to content

Commit d7ba570

Browse files
authored
Merge pull request #216 from Ondkloss/feature/fine_grained
Add support for fine-grained tokens (continued)
2 parents d163cd6 + b277baa commit d7ba570

2 files changed

Lines changed: 71 additions & 29 deletions

File tree

README.rst

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ Usage
2929

3030
CLI Usage is as follows::
3131

32-
github-backup [-h] [-u USERNAME] [-p PASSWORD] [-t TOKEN] [--as-app]
33-
[-o OUTPUT_DIRECTORY] [-l LOG_LEVEL] [-i] [--starred]
34-
[--all-starred] [--watched] [--followers] [--following]
35-
[--all] [--issues] [--issue-comments] [--issue-events]
36-
[--pulls] [--pull-comments] [--pull-commits]
37-
[--pull-details] [--labels] [--hooks] [--milestones]
38-
[--repositories] [--bare] [--lfs] [--wikis] [--gists]
39-
[--starred-gists] [--skip-archived] [--skip-existing]
40-
[-L [LANGUAGES ...]] [-N NAME_REGEX] [-H GITHUB_HOST]
41-
[-O] [-R REPOSITORY] [-P] [-F] [--prefer-ssh] [-v]
32+
github-backup [-h] [-u USERNAME] [-p PASSWORD] [-t TOKEN_CLASSIC]
33+
[-f TOKEN_FINE] [--as-app] [-o OUTPUT_DIRECTORY]
34+
[-l LOG_LEVEL] [-i] [--starred] [--all-starred]
35+
[--watched] [--followers] [--following] [--all] [--issues]
36+
[--issue-comments] [--issue-events] [--pulls]
37+
[--pull-comments] [--pull-commits] [--pull-details]
38+
[--labels] [--hooks] [--milestones] [--repositories]
39+
[--bare] [--lfs] [--wikis] [--gists] [--starred-gists]
40+
[--skip-archived] [--skip-existing] [-L [LANGUAGES ...]]
41+
[-N NAME_REGEX] [-H GITHUB_HOST] [-O] [-R REPOSITORY]
42+
[-P] [-F] [--prefer-ssh] [-v]
4243
[--keychain-name OSX_KEYCHAIN_ITEM_NAME]
4344
[--keychain-account OSX_KEYCHAIN_ITEM_ACCOUNT]
4445
[--releases] [--assets] [--exclude [REPOSITORY [REPOSITORY ...]]
@@ -57,7 +58,10 @@ CLI Usage is as follows::
5758
-p PASSWORD, --password PASSWORD
5859
password for basic auth. If a username is given but
5960
not a password, the password will be prompted for.
60-
-t TOKEN, --token TOKEN
61+
-f TOKEN_FINE, --token-fine TOKEN_FINE
62+
fine-grained personal access token or path to token
63+
(file://...)
64+
-t TOKEN_CLASSIC, --token TOKEN_CLASSIC
6165
personal access, OAuth, or JSON Web token, or path to
6266
token (file://...)
6367
--as-app authenticate as github app instead of as a user.
@@ -163,13 +167,13 @@ Backup all repositories, including private ones::
163167
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
164168
github-backup WhiteHouse --token $ACCESS_TOKEN --organization --output-directory /tmp/white-house --repositories --private
165169

166-
Backup a single organization repository with everything else (wiki, pull requests, comments, issues etc)::
170+
Use a fine-grained access token to backup a single organization repository with everything else (wiki, pull requests, comments, issues etc)::
167171

168172
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
169173
ORGANIZATION=docker
170174
REPO=cli
171175
# e.g. git@github.com:docker/cli.git
172-
github-backup $ORGANIZATION -P -t $ACCESS_TOKEN -o . --all -O -R $REPO
176+
github-backup $ORGANIZATION -P -f $ACCESS_TOKEN -o . --all -O -R $REPO
173177

174178
Testing
175179
=======

github_backup/github_backup.py

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
VERSION = "unknown"
3737

3838
FNULL = open(os.devnull, "w")
39+
FILE_URI_PREFIX = "file://"
3940
logger = logging.getLogger(__name__)
4041

4142

@@ -126,9 +127,15 @@ def parse_args(args=None):
126127
parser.add_argument(
127128
"-t",
128129
"--token",
129-
dest="token",
130+
dest="token_classic",
130131
help="personal access, OAuth, or JSON Web token, or path to token (file://...)",
131132
) # noqa
133+
parser.add_argument(
134+
"-f",
135+
"--token-fine",
136+
dest="token_fine",
137+
help="fine-grained personal access token (github_pat_....), or path to token (file://...)",
138+
) # noqa
132139
parser.add_argument(
133140
"--as-app",
134141
action="store_true",
@@ -431,18 +438,27 @@ def get_auth(args, encode=True, for_git_cli=False):
431438
raise Exception(
432439
"You must specify both name and account fields for osx keychain password items"
433440
)
434-
elif args.token:
435-
_path_specifier = "file://"
436-
if args.token.startswith(_path_specifier):
437-
path_specifier_len = len(_path_specifier)
438-
args.token = open(args.token[path_specifier_len:], "rt").readline().strip()
441+
elif args.token_fine:
442+
if args.token_fine.startswith(FILE_URI_PREFIX):
443+
args.token_fine = read_file_contents(args.token_fine)
444+
445+
if args.token_fine.startswith("github_pat_"):
446+
auth = args.token_fine
447+
else:
448+
raise Exception(
449+
"Fine-grained token supplied does not look like a GitHub PAT"
450+
)
451+
elif args.token_classic:
452+
if args.token_classic.startswith(FILE_URI_PREFIX):
453+
args.token_classic = read_file_contents(args.token_classic)
454+
439455
if not args.as_app:
440-
auth = args.token + ":" + "x-oauth-basic"
456+
auth = args.token_classic + ":" + "x-oauth-basic"
441457
else:
442458
if not for_git_cli:
443-
auth = args.token
459+
auth = args.token_classic
444460
else:
445-
auth = "x-access-token:" + args.token
461+
auth = "x-access-token:" + args.token_classic
446462
elif args.username:
447463
if not args.password:
448464
args.password = getpass.getpass()
@@ -457,7 +473,7 @@ def get_auth(args, encode=True, for_git_cli=False):
457473
if not auth:
458474
return None
459475

460-
if not encode:
476+
if not encode or args.token_fine is not None:
461477
return auth
462478

463479
return base64.b64encode(auth.encode("ascii"))
@@ -481,6 +497,10 @@ def get_github_host(args):
481497
return host
482498

483499

500+
def read_file_contents(file_uri):
501+
return open(file_uri[len(FILE_URI_PREFIX) :], "rt").readline().strip()
502+
503+
484504
def get_github_repo_url(args, repository):
485505
if repository.get("is_gist"):
486506
if args.prefer_ssh:
@@ -503,7 +523,7 @@ def get_github_repo_url(args, repository):
503523
auth = get_auth(args, encode=False, for_git_cli=True)
504524
if auth:
505525
repo_url = "https://{0}@{1}/{2}/{3}.git".format(
506-
auth,
526+
auth if args.token_fine is None else "oauth2:" + auth,
507527
get_github_host(args),
508528
repository["owner"]["login"],
509529
repository["name"],
@@ -523,7 +543,13 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
523543
while True:
524544
page = page + 1
525545
request = _construct_request(
526-
per_page, page, query_args, template, auth, as_app=args.as_app
546+
per_page,
547+
page,
548+
query_args,
549+
template,
550+
auth,
551+
as_app=args.as_app,
552+
fine=True if args.token_fine is not None else False,
527553
) # noqa
528554
r, errors = _get_response(request, auth, template)
529555

@@ -559,7 +585,13 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
559585
retries += 1
560586
time.sleep(5)
561587
request = _construct_request(
562-
per_page, page, query_args, template, auth, as_app=args.as_app
588+
per_page,
589+
page,
590+
query_args,
591+
template,
592+
auth,
593+
as_app=args.as_app,
594+
fine=True if args.token_fine is not None else False,
563595
) # noqa
564596
r, errors = _get_response(request, auth, template)
565597

@@ -643,17 +675,23 @@ def _get_response(request, auth, template):
643675
return r, errors
644676

645677

646-
def _construct_request(per_page, page, query_args, template, auth, as_app=None):
678+
def _construct_request(
679+
per_page, page, query_args, template, auth, as_app=None, fine=False
680+
):
647681
querystring = urlencode(
648682
dict(
649-
list({"per_page": per_page, "page": page}.items()) + list(query_args.items())
683+
list({"per_page": per_page, "page": page}.items())
684+
+ list(query_args.items())
650685
)
651686
)
652687

653688
request = Request(template + "?" + querystring)
654689
if auth is not None:
655690
if not as_app:
656-
request.add_header("Authorization", "Basic ".encode("ascii") + auth)
691+
if fine:
692+
request.add_header("Authorization", "token " + auth)
693+
else:
694+
request.add_header("Authorization", "Basic ".encode("ascii") + auth)
657695
else:
658696
auth = auth.encode("ascii")
659697
request.add_header("Authorization", "token ".encode("ascii") + auth)

0 commit comments

Comments
 (0)