Skip to content

Commit

Permalink
add oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
samkuehn committed Jul 16, 2019
1 parent 630e0af commit 58c702b
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 60 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ an `git remote update` will be run for git repos.
## Installation

```bash
pip install https://github.com/samkuehn/bitbucket-backup/archive/master.zip
pip install -u https://github.com/samkuehn/bitbucket-backup/archive/master.zip
```

## Quickstart
Expand All @@ -31,6 +31,19 @@ If would like, you can use app passwords instead of your password used to login
The password must have read repositories permission.
<https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html>

## OAuth authentication

You can use OAuth authentication if you do not want to use username/password. There are 4 steps to using OAuth.

1. Create OAuth consumer keys here: https://bitbucket.org/account/user/{username}/api
2. Make sure that your consumer has the following permissions:
* Account: Read
* Projects: Read
* Repositories: Read
3. Specify the -k <oauth_key> -s <oauth_secret> flags
4. Make sure requests-oauthlib is installed `pip install requests-oauthlib`


## Requirements

You do need to have your ssh keys uploaded for the computer that you are running the backup on.
Expand Down
125 changes: 67 additions & 58 deletions backup.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
#!/usr/bin/env python
import argparse
import base64
import datetime
import os
import subprocess
import sys
from getpass import getpass

try:
from urllib.error import HTTPError, URLError
except ImportError:
from urllib2 import HTTPError, URLError

try:
from urllib.request import Request, urlopen
except ImportError:
from urllib2 import Request, urlopen
import requests
from requests.auth import HTTPBasicAuth

try:
from urllib.parse import quote
Expand All @@ -27,11 +19,6 @@
except NameError:
pass

try:
import json
except ImportError:
import simplejson as json

try:
_range = xrange
except NameError:
Expand Down Expand Up @@ -111,39 +98,57 @@ def fetch_lfs_content(backup_dir):
exec_cmd(command, stop_on_error=False)


def get_repositories(method="GET", username=None, password=None, team=None, data=None):
url = "https://api.bitbucket.org/2.0/repositories/{}/".format(team or username)

header = "%s:%s" % (username, password)
header = {
"Authorization": "Basic %s"
% (base64.b64encode(header.encode("utf_8")).decode("utf_8").strip())
}
request = Request(url, data, header)
request.get_method = lambda: method
result = urlopen(request).read().decode("utf-8")
repos_data = json.loads(result)
def get_repositories(
username=None, password=None, oauth_key=None, oauth_secret=None, team=None
):
auth = None
repos = []
for repo in repos_data.get("values"):
repos.append(repo)
while repos_data.get("next"):
request = Request(repos_data.get("next"), data, header)
result = urlopen(request).read().decode("utf-8")
repos_data = json.loads(result)
try:
if all((oauth_key, oauth_secret)):
from requests_oauthlib import OAuth1
auth = OAuth1(oauth_key, oauth_secret)
if all((username, password)):
auth = HTTPBasicAuth(username, password)
if auth is None:
exit("Must provide username/password or oath credentials")
if not team or username:
response = requests.get("https://api.bitbucket.org/2.0/user/", auth=auth)
username = response.json().get("username")
url = "https://api.bitbucket.org/2.0/repositories/{}/".format(team or username)

response = requests.get(url, auth=auth)
response.raise_for_status()
repos_data = response.json()
for repo in repos_data.get("values"):
repos.append(repo)
while repos_data.get("next"):
response = requests.get(repos_data.get("next"), auth=auth)
repos_data = response.json()
for repo in repos_data.get("values"):
repos.append(repo)
except requests.exceptions.RequestException as e:

if e.response.status_code == 401:
exit(
"Unauthorized! Check your credentials and try again.", 22
) # EINVAL - Invalid argument
else:
exit(
"Connection Error! Bitbucket returned HTTP error [%s]."
% e.response.status_code
)
return repos


def clone_repo(
repo,
backup_dir,
http,
username,
password,
mirror=False,
with_wiki=False,
fetch_lfs=False,
repo,
backup_dir,
http,
username,
password,
mirror=False,
with_wiki=False,
fetch_lfs=False,
):
global _quiet, _verbose
scm = repo.get("scm")
Expand Down Expand Up @@ -180,7 +185,7 @@ def clone_repo(
command = "%s [email protected]:%s/%s.git" % (
git_command,
owner_url,
quote(username),
slug_url,
)
if not command:
exit("could not build command (scm [%s] not recognized?)" % scm)
Expand Down Expand Up @@ -220,6 +225,12 @@ def main():
parser = argparse.ArgumentParser(description="Usage: %prog [options] ")
parser.add_argument("-u", "--username", dest="username", help="Bitbucket username")
parser.add_argument("-p", "--password", dest="password", help="Bitbucket password")
parser.add_argument(
"-k", "--oauth-key", dest="oauth_key", help="Bitbucket oauth key"
)
parser.add_argument(
"-s", "--oauth-secret", dest="oauth_secret", help="Bitbucket oauth secret"
)
parser.add_argument("-t", "--team", dest="team", help="Bitbucket team")
parser.add_argument(
"-l", "--location", dest="location", help="Local backup location"
Expand Down Expand Up @@ -285,6 +296,8 @@ def main():
location = args.location
username = args.username
password = args.password
oauth_key = args.oauth_key
oauth_secret = args.oauth_secret
http = args.http
max_attempts = args.attempts
global _quiet
Expand All @@ -298,17 +311,24 @@ def main():
_verbose = False # override in case both are selected
team = args.team

if not username:
username = input("Enter bitbucket username: ")
if not password:
password = getpass(prompt="Enter your bitbucket password: ")
if not all((oauth_key, oauth_secret)):
if not username:
username = input("Enter bitbucket username: ")
if not password:
password = getpass(prompt="Enter your bitbucket password: ")
if not location:
location = input("Enter local location to backup to: ")
location = os.path.abspath(location)

# ok to proceed
try:
repos = get_repositories(username=username, password=password, team=team)
repos = get_repositories(
username=username,
password=password,
oauth_key=oauth_key,
oauth_secret=oauth_secret,
team=team,
)
repos = sorted(repos, key=lambda repo_: repo_.get("name"))
if not repos:
print(
Expand Down Expand Up @@ -366,17 +386,6 @@ def main():
if args.compress:
compress(repo, location)
debug("Finished!", True)
except HTTPError as err:
if err.code == 401:
exit(
"Unauthorized! Check your credentials and try again.", 22
) # EINVAL - Invalid argument
else:
exit("Connection Error! Bitbucket returned HTTP error [%s]." % err.code)
except URLError as e:
exit(
"Unable to reach Bitbucket: %s." % e.reason, 101
) # ENETUNREACH - Network is unreachable
except (KeyboardInterrupt, SystemExit):
exit(
"Operation cancelled. There might be inconsistent data in location directory.",
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ipdb==0.12
requests==2.22.0
requests-oauthlib==1.2.0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

TEST_REQUIRES = ["nose", "unittest2"]

INSTALL_REQUIRES = []
INSTALL_REQUIRES = ["requests", "requests-oauthlib"]
try:
import argparse # noqa
except ImportError:
Expand Down

0 comments on commit 58c702b

Please sign in to comment.