Skip to content

Commit

Permalink
Use playwright for GH signup
Browse files Browse the repository at this point in the history
  • Loading branch information
fredg02 committed Aug 16, 2024
1 parent 26cafc5 commit 08d94ae
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 24 deletions.
225 changes: 225 additions & 0 deletions github/playwright/gh_signup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import sys, os, subprocess, requests, common
import pyperclip
from playwright.sync_api import sync_playwright, Page, Error, expect

def signup(page, project_name, username, password, email):
response = page.goto("https://github.com/signup")

assert response is not None
if not response.ok:
raise RuntimeError(f"unable to load GitHub login page: {response.status}")

expect(page.locator('#email')).to_be_visible(timeout=10000)
page.locator('#email').fill(email)
page.get_by_role("button", name="Continue").click()
page.locator('#password').fill(password)
page.get_by_role("button", name="Continue").click()
page.locator("#login").fill(username)
page.get_by_role("button", name="Continue").click()
#EMail preferences (don't select check box)

# little dance to get the "Continue" button to show up
for x in range(10):
if page.get_by_text("Receive occasional product").is_visible():
page.wait_for_timeout(2000)
page.get_by_label("Email preferences").uncheck()
page.wait_for_timeout(2000)
page.get_by_label("Email preferences").uncheck()
page.get_by_role("button", name="Continue").click()
else:
print(x)
break

print ("Please click on \"Verify\" button.")

# MANUAL INTERVENTION
# 1. Click on "Verify" button
# 2. Solve Captcha
# 3. Click on "Create account"
# 4. Enter Launch code

expect(page.get_by_role("heading", name="Sign in to GitHub")).to_be_visible(timeout=60000)

# login page (new)
if page.get_by_role("heading", name="Sign in to GitHub").is_visible():
page.get_by_label("Username or email address").click()
page.get_by_label("Username or email address").fill(username)
page.get_by_label("Password").click()
page.get_by_label("Password").fill(password)
page.get_by_role("button", name="Sign in", exact=True).click()

# 4. Skip personalization
if page.get_by_role("link", name="Skip personalization").is_visible():
page.get_by_role("link", name="Skip personalization").click()

#TODO:
#expect(page.get_by_text("Dashboard")).to_be_visible(timeout=30000)

page.wait_for_timeout(2000)

def setup_2fa(page, project_name):
_DOWNLOAD_DIR = "/home/fr3d/Downloads/"
# Navigate to 2FA settings
common.open_settings(page)
page.get_by_role("link", name="Password and authentication").click()

# Check if 2FA is already enabled
page.get_by_role("heading", name="Two-factor authentication", exact=True).click() # This click is required, otherwise the next elements are not found!?
if not page.get_by_role("heading", name="Two-factor authentication is not enabled yet.").is_visible():
print ("2FA is already set up. Skipping.")
return

page.get_by_role("link", name="Enable two-factor authentication").click()
page.get_by_role("button", name="setup key").click()

# get 2FA seed text
#TODO: is there a simpler and more reliable way??
expect(page.get_by_role("dialog", name="Your two-factor secret")).to_be_visible()
page.wait_for_timeout(2000)
twofa_seed = page.locator('xpath=//div[@data-target="two-factor-setup-verification.mashedSecret"]').all_inner_texts()
twofa_seed = ' '.join(twofa_seed)

if not twofa_seed:
page.page.get_by_role("dialog", name="Your two-factor secret").press("Escape")
page.get_by_role("button", name="setup key").click()
if not twofa_seed:
sys.exit("2FA seed text not found!")

print ("Found 2FA seed text: " + twofa_seed)
#input('Press any key to continue\n')

# add 2FA seed to pass
#os.popen("echo " + twofa_seed +" | pass insert cbi-pass/bots/"+ project_name + "/github.com/2FA-seed").read()
#os.popen("echo \"hello" + twofa_seed +"\"").read()
subprocess.check_output("echo \"" + twofa_seed +"\" | pass insert -m cbi-pass/bots/"+ project_name + "/github.com/2FA-seed", shell=True)

# get OTP from pass
twofa_token = os.popen("pass otp cbi-pass/bots/"+ project_name + "/github.com/2FA-seed").read()
print ("2FA token: " + twofa_token)

# enter OTP
page.get_by_role("button", name="Close").click()
page.get_by_role("textbox", name="XXXXXX").fill(twofa_token)
page.get_by_text("Verify the code from the app").click()

# get recovery codes
page.locator(".two-factor-recovery-code").nth(0).text_content() # The next line does not work without this one !?!??!
twofa_codes = page.locator(".two-factor-recovery-code").all_text_contents()
twofa_codes = '\n'.join(twofa_codes)

print ("Found 2FA recovery codes:\n" + twofa_codes)
#input('Press any key to continue\n')

# add 2FA codes to pass
subprocess.check_output("echo \"" + twofa_codes +"\" | pass insert -m cbi-pass/bots/"+ project_name + "/github.com/2FA-recovery-codes", shell=True)

# download recovery codes
#FIXME
with page.expect_download() as download_info:
page.get_by_role("button", name="Download").click()
download = download_info.value
download.save_as(_DOWNLOAD_DIR + download.suggested_filename)

#input('Press any key to continue\n')
page.get_by_role("button", name="I have saved my recovery codes").click()
page.get_by_role("button", name="Done").click()

def setup_ssh(page, project_name, ssh_pub_key, email):
# navigate to SSH settings
common.open_settings(page)
page.get_by_role("link", name="SSH and GPG keys").click()

# Check if SSH public key has already been added
page.get_by_role("heading", name="SSH keys").click() # This click is required, otherwise the next elements are not found!?
if page.get_by_role("heading", name="Authentication keys").is_visible():
if page.get_by_text(email).is_visible():
# Take screenshot
page.screenshot(path="ssh_key_already_exists.png")
print ("SSH key has already been added. See screenshot.")
return

page.get_by_role("link", name="New SSH key").click()
page.get_by_placeholder("Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'").fill(ssh_pub_key)
page.get_by_role("button", name="Add SSH key").click()

def setup_token(page, project_name):
short_name = common.get_project_shortname(project_name)

common.nav_to_token_settings(page)

# Check if token has already been added
page.get_by_role("heading", name="Personal access tokens (classic)").click() # This click is required, otherwise the next elements are not found!?
if page.get_by_role("link", name="Jenkins GitHub Plugin token https://ci.eclipse.org/" + short_name).is_visible():
print ("Token has already been added. Skipping.")
return

page.get_by_role("button", name="Generate new token").click()
page.get_by_role("menuitem", name="Generate new token (classic) For general use").click()
page.get_by_label("Note").fill("Jenkins GitHub Plugin token https://ci.eclipse.org/" + short_name)
page.get_by_role("combobox", name="Expiration*").select_option("none")
page.get_by_label("repo:status\n \n\n \n \n Access commit status").check()
page.get_by_label("public_repo\n \n\n \n \n Access public repositories").check()
page.get_by_label("admin:repo_hook\n \n\n \n \n Full control of repository hooks").check()
page.get_by_label("admin:org_hook\n \n\n \n \n Full control of organization hooks").check()
page.get_by_role("button", name="Generate token").click()
page.get_by_role("button", name="Copy token").click()

# get token from clipboard
api_token = pyperclip.paste()
print ("api_token: " + api_token)
# TODO: check that api_token is not empty

# add token to pass
common.add_to_pass(project_name, api_token, "api-token")

### MAIN
def main():
_DEFAULT_TIMEOUT = 10000

if len(sys.argv) < 2:
print ("ERROR: project name must be set")
sys.exit(1)
else:
project_name=sys.argv[1]
print ("Project name: " + project_name)

print("opening browser window")
with sync_playwright() as playwright:
browser = playwright.firefox.launch(headless=False)
context = browser.new_context(no_viewport=True)

page = context.new_page()
page.set_default_timeout(_DEFAULT_TIMEOUT)

username = common.get_pass_creds(project_name, "username")
password = common.get_pass_creds(project_name, "password")
email = common.get_pass_creds(project_name, "email")
ssh_pubkey = common.get_pass_creds(project_name, "id_rsa.pub")

#check if GH account has been set up or not
url = "https://github.com/" + username.strip()
r = requests.head(url)
print("Status Code for " + url + ": " + str(r.status_code))
if r.status_code == 200:
print("User account exists, trying to login.")
common.login(page, project_name, username, password)
else:
print("User account does not exist, signing up.")
signup(page, project_name, username, password, email)

expect(page.get_by_role("heading", name="Home", exact=True)).to_be_visible(timeout=30000)

#TODO: make more robust: skip if ssh/token/2FA has already been set up
setup_ssh(page, project_name, ssh_pubkey, email)
setup_token(page, project_name)
setup_2fa(page, project_name)

#input('Press any key to continue\n')
common.signout(page)

page.close()
context.close()
browser.close()

if __name__ == "__main__":
main()
27 changes: 3 additions & 24 deletions github/setup_github_bot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,8 @@ create_github_credentials() {
}

set_up_github_account() {
# automate?
cat <<EOF
# Setting up GitHub bot account...
==================================
* Set up GitHub bot account (https://github.com/signup)
* Take credentials from pass
* Verify email
* Add SSH public key to GitHub bot account (Settings -> SSh and GPG keys -> New SSH key)
* Create API token (Settings -> Developer Settings -> Personal access tokens)
* API token
* Name: Jenkins GitHub Plugin token https://ci.eclipse.org/${SHORT_NAME}
* Expiration: No expiration
* Scopes: repo:status, public_repo, admin:repo_hook
* Add token to pass (api-token)
* Add GitHub bot to project’s GitHub org (invite via webmaster account)
EOF
#TODO: open private browser window
_open_url "https://github.com/signup"
read -rsp $'Once you are done, press any key to continue...\n' -n1

#TODO: read tokens from stdin and add them to pass

echo "# Setting up GitHub bot account..."
python "playwright/gh_signup.py" "${PROJECT_NAME}"
}

add_jenkins_credentials() {
Expand Down Expand Up @@ -119,7 +98,7 @@ create_github_credentials

set_up_github_account

update_projects_bot_api
_question_action "update the projects bot API" update_projects_bot_api

#check if project has a Jenkins instance
if [[ -d "${JIRO_ROOT_FOLDER}/instances/${PROJECT_NAME}" ]]; then
Expand Down

0 comments on commit 08d94ae

Please sign in to comment.