File: github_credential_manager.py

package info (click to toggle)
azure-devops-cli-extension 1.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 20,384 kB
  • sloc: python: 160,782; xml: 198; makefile: 56; sh: 51
file content (90 lines) | stat: -rw-r--r-- 4,421 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import base64
import requests
from knack.prompting import prompt, prompt_pass
from knack.log import get_logger
from knack.util import CLIError
from azext_devops.dev.common.utils import datetime_now_as_string, singleton
from azext_devops.dev.common.const import AZ_DEVOPS_GITHUB_PAT_ENVKEY

logger = get_logger(__name__)


@singleton
class GithubCredentialManager():
    """ GithubCredentialManager
    """
    def __init__(self):
        self.username = None
        self.password = None
        self.token = None

    def _create_token(self, note=None):
        logger.warning('We need to create a Personal Access Token to communicate with GitHub. '
                       'A new PAT with scopes (admin:repo_hook, repo, user) will be created.')
        logger.warning('You can set the PAT in the environment variable (%s) to avoid getting prompted.',
                       AZ_DEVOPS_GITHUB_PAT_ENVKEY)
        self.username = prompt(msg='Enter your GitHub username (leave blank for using already generated PAT): ')
        print('')
        if not self.username:
            while not self.token:
                self.token = prompt_pass(msg='Enter your GitHub PAT: ', help_string='Generate a Personal Access Token '
                                         'with approproate permissions from GitHub Developer settings and paste here.')
            print('')
            return
        self.password = prompt_pass(msg='Enter your GitHub password: ', confirm=True)
        print('')
        if not note:
            note = "AzureDevopsCLIExtensionToken_" + datetime_now_as_string()
        encoded_pass = base64.b64encode(self.username.encode('utf-8') + b':' + self.password.encode('utf-8'))
        basic_auth = 'basic ' + encoded_pass.decode("utf-8")
        request_body = {
            'scopes': [
                'admin:repo_hook',
                'repo',
                'user'
            ],
            'note': note
        }
        headers = {'Content-Type': 'application/json' + '; charset=utf-8',
                   'Accept': 'application/json',
                   'Authorization': basic_auth}
        response = self.post_authorization_request(headers=headers, body=request_body)
        if (response.status_code == 401 and response.headers.get('X-GitHub-OTP') and
                response.headers.get('X-GitHub-OTP').startswith('required')):
            two_factor_code = None
            while not two_factor_code:
                two_factor_code = prompt_pass(msg='Enter your two factor authentication code: ')
            print('')
            headers = {'Content-Type': 'application/json' + '; charset=utf-8',
                       'Accept': 'application/json',
                       'Authorization': basic_auth,
                       'X-GitHub-OTP': two_factor_code}
            response = self.post_authorization_request(headers=headers, body=request_body)
        import json
        response_json = json.loads(response.content)
        if response.status_code == 200 or response.status_code == 201:
            logger.warning('Created new personal access token with scopes (admin:repo_hook, repo, user). Name: %s '
                           'You can revoke this from your GitHub settings if the pipeline is no longer required.',
                           note)
            self.token = response_json['token']
        else:
            raise CLIError('Could not create a Personal Access Token for GitHub. Check your credentials and try again.')

    def post_authorization_request(self, headers, body):  # pylint: disable=no-self-use
        return requests.post('https://api.github.com/authorizations',
                             json=body, headers=headers)

    def get_token(self, note=None):
        import os
        github_pat = os.getenv(AZ_DEVOPS_GITHUB_PAT_ENVKEY, None)
        if github_pat:
            logger.warning('Using GitHub PAT token found in environment variable (%s).', AZ_DEVOPS_GITHUB_PAT_ENVKEY)
            return github_pat
        if not self.token:
            self._create_token(note=note)
        return self.token