Skip to content
This repository has been archived by the owner on Jan 12, 2022. It is now read-only.

Commit

Permalink
AMOOAuth added, not working
Browse files Browse the repository at this point in the history
  • Loading branch information
zalun committed Aug 8, 2011
1 parent c41154b commit 6eeaa28
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 0 deletions.
27 changes: 27 additions & 0 deletions apps/amo/tasks.py
@@ -0,0 +1,27 @@
import commonware.log
import time
from statsd import statsd

from celery.decorators import task

from jetpack.models import PackageRevision
from utils.helpers import get_random_string
from xpi import xpi_utils


log = commonware.log.getLogger('f.celery')


@task
def upload_to_amo(rev_pk):
"""Build XPI and upload to AMO
Read result and save amo_id
"""
tstart = time.time()
hashtag = get_random_string(10)
revision = PackageRevision.objects.get(pk=rev_pk)
response = revision.build_xpi(
hashtag=hashtag,
tstart=tstart)
# use created XPI
revision.upload_to_amo(hashtag)
4 changes: 4 additions & 0 deletions apps/amo/urls.py
@@ -0,0 +1,4 @@
from django.conf.urls.defaults import url, patterns

urlpatterns = patterns('amo.views',
url(r'^upload_to_amo/(?P<pk>\d+)/', 'upload_to_amo', name='amo_upload'))
9 changes: 9 additions & 0 deletions apps/amo/views.py
@@ -0,0 +1,9 @@
from django.shortcuts import render_to_response

from amo import tasks

def upload_to_amo(request, pk):
"""Upload a XPI to AMO
"""
tasks.upload_to_amo.delay(pk)
return HttpResponse('{"delayed": true}')
40 changes: 40 additions & 0 deletions apps/jetpack/models.py
Expand Up @@ -40,6 +40,7 @@
from utils.exceptions import SimpleException
from utils.helpers import pathify, alphanum, alphanum_plus
from utils.os_utils import make_path
from utils.amo import AMOOAuth
from xpi import xpi_utils

log = commonware.log.getLogger('f.jetpack')
Expand Down Expand Up @@ -315,6 +316,14 @@ def get_download_xpi_url(self):
'jp_addon_revision_xpi',
args=[self.package.id_number, self.revision_number])

def get_upload_to_amo_url(self):
" returns URL to upload to AMO "
if self.package.type != 'a':
raise Exception('Only Add-ons might be uploaded to AMO')
return reverse(
'amo_upload',
args=[self.pk])

def get_copy_url(self):
" returns URL to copy the package "
return reverse(
Expand Down Expand Up @@ -1340,6 +1349,37 @@ class Meta:

objects = PackageManager()

##################
# AMO Integration

def upload_to_amo(self, hashtag):
"""Uploads Package to AMO, updates or creates as a new Addon
"""
# open XPI File
xpi_file = open(os.path.join('%s.xpi' % hashtag))
# upload
data = {'xpi': xpi_file,
'builtin': 0,
'name': 'FREEDOM',
'text': 'This is FREE!',
'platform': 'linux',
'authenticate_as': 2}
amo = AMOOAuth(domain=AMOOAUTH_DOMAIN, port=AMOOAUTH_PORT,
protocol=AMOOAUTH_PROTOCOL)
amo.set_consumer(consumer_key=AMOOAUTH_CONSUMERKEY,
consumer_secret=AMOOAUTH_CONSUMERSECRET)
if self.amo_id:
# update addon on AMO
# update jetpack ID if needed
pass
else:
# create addon on AMO
response = amo.create_addon(data)
# set amo_id
# set jetpack ID

print response

##################
# Methods

Expand Down
3 changes: 3 additions & 0 deletions apps/jetpack/templates/addon_edit.html
Expand Up @@ -43,6 +43,9 @@
</li>
<li id="download" title="Download" class="UI_Editor_Menu_Button Icon_download">
<a target="_new" href="{{ revision.get_download_xpi_url }}"><span></span></a>
</li>
<li id="upload_to_amo" title="Upload" class="UI_Editor_Menu_Button Icon_upload">
<a target="_new" href="{{ revision.get_upload_to_amo_url }}"><span></span></a>
</li>
<li class="UI_Editor_Menu_Separator"></li>
{% endblock %}
Expand Down
7 changes: 7 additions & 0 deletions settings.py
Expand Up @@ -140,6 +140,13 @@
DOMAIN = "builder.addons.mozilla.org"
SITE_URL = "https://%s" % DOMAIN

# AMO OAUTH DATA
AMOOAUTH_DOMAIN = "addons.mozilla.org"
AMOOAUTH_PORT = 8043
AMOOAUTH_PROTOCOL = "https"
AMOOAUTH_CONSUMERKEY = "key"
AMOOAUTH_CONSUMERSECRET = "secret"

URLOPEN_TIMEOUT = 4 # default timeout for urllib2.urlopen (seconds)

# set it in settings_local.py if AMO auth should be used
Expand Down
3 changes: 3 additions & 0 deletions urls.py
Expand Up @@ -24,6 +24,9 @@
# XPI build
(r'^xpi/', include('xpi.urls')),

# AMO upload
(r'^amo/', include('amo.urls')),

# /docs are an Apache rewrite

(r'^tutorial/', include('tutorial.urls')),
Expand Down
228 changes: 228 additions & 0 deletions utils/amo.py
@@ -0,0 +1,228 @@
"""
A class to interact with AMO's api, using OAuth.
Ripped off from Daves test_oauth.py and some notes from python-oauth2
"""
# Wherein import almost every http or urllib in Python
import urllib
import urllib2
from urlparse import urlparse, urlunparse, parse_qsl
import httplib2
import oauth2 as oauth
import os
import re
import time
import json
import mimetools

from helpers import encode_multipart, data_keys

# AMO Specific end points
urls = {
'login': '/users/login',
'request_token': '/oauth/request_token/',
'access_token': '/oauth/access_token/',
'authorize': '/oauth/authorize/',
'user': '/api/2/user/',
'addon': '/api/2/addons/',
'update': '/api/2/update/',
'perf': '/api/2/performance/',
}

storage_file = os.path.join(os.path.expanduser('~'), '.amo-oauth')
boundary = mimetools.choose_boundary()

old = httplib2.Http.__init__


# Ouch, I'll go to hell for this.
def hack(self, **kw):
kw['disable_ssl_certificate_validation'] = True
return old(self, **kw)

httplib2.Http.__init__ = hack


class AMOOAuth:
"""
A base class to authenticate and work with AMO OAuth.
"""
signature_method = oauth.SignatureMethod_HMAC_SHA1()

def __init__(self, domain='addons.mozilla.org', protocol='https',
port=443, prefix='', three_legged=False):
self.data = self.read_storage()
self.domain = domain
self.protocol = protocol
self.port = port
self.prefix = prefix
self.three_legged = three_legged

def set_consumer(self, consumer_key, consumer_secret, save_storage=True):
self.should_save_storage = save_storage
self.data['consumer_key'] = consumer_key
self.data['consumer_secret'] = consumer_secret
if self.should_save_storage:
self.save_storage()

def get_consumer(self):
return oauth.Consumer(self.data['consumer_key'],
self.data['consumer_secret'])

def get_access(self):
return oauth.Token(self.data['access_token']['oauth_token'],
self.data['access_token']['oauth_token_secret'])

def has_access_token(self):
return not self.three_legged or 'access_token' in self.data

def read_storage(self):
if self.should_save_storage and os.path.exists(storage_file):
try:
return json.load(open(storage_file, 'r'))
except ValueError:
pass
return {}

def url(self, key):
return urlunparse((self.protocol, '%s:%s' % (self.domain, self.port),
'%s/en-US/firefox%s' % (self.prefix, urls[key]),
'', '', ''))

def shorten(self, url):
return urlunparse(['', ''] + list(urlparse(url)[2:]))

def save_storage(self):
json.dump(self.data, open(storage_file, 'w'))

def get_csrf(self, content):
return re.search("name='csrfmiddlewaretoken' value='(.*?)'",
content).groups()[0]

def _request(self, token, method, url, data={}, headers={}, **kw):
parameters = data_keys(data)
parameters.update(kw)
request = (oauth.Request
.from_consumer_and_token(self.get_consumer(), token,
method, url, parameters))
request.sign_request(self.signature_method, self.get_consumer(), token)
client = httplib2.Http()
if data and method == 'POST':
data = encode_multipart(boundary, data)
headers.update({'Content-Type':
'multipart/form-data; boundary=%s' % boundary})
else:
data = urllib.urlencode(data)
return client.request(request.to_url(), method=method,
headers=headers, body=data)

def authenticate(self, username=None, password=None):
"""
This is only for the more convoluted three legged approach.
1. Login into AMO.
2. Get a request token for the consumer.
3. Approve the consumer.
4. Get an access token.
"""
# First we need to login to AMO, this takes a few steps.
# If this was being done in a browser, this wouldn't matter.
#
# This callback is pretty academic, but required so we can get
# verification token.
callback = 'http://foo.com/'

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
urllib2.install_opener(opener)
res = opener.open(self.url('login'))
assert res.code == 200

# get the CSRF middleware token
if password is None:
password = raw_input('Enter password: ')

csrf = self.get_csrf(res.read())
data = urllib.urlencode({'username': username,
'password': password,
'csrfmiddlewaretoken': csrf})
res = opener.open(self.url('login'), data)
assert res.code == 200

# We need these headers to be able to post to the authorize method
cookies = {}
# Need to find a better way to find the handler, -2 is fragile.
for cookie in opener.handlers[-2].cookiejar:
if cookie.name == 'sessionid':
cookies = {'Cookie': '%s=%s' % (cookie.name, cookie.value)}
# Step 1 completed, we can now be logged in for any future requests

# Step 2, get a request token.
resp, content = self._request(None, 'GET', self.url('request_token'),
oauth_callback=callback)
assert resp['status'] == '200', 'Status was: %s' % resp.status

request_token = dict(parse_qsl(content))
assert request_token
token = oauth.Token(request_token['oauth_token'],
request_token['oauth_token_secret'])

# Step 3, authorize the access of this consumer for this user account.
resp, content = self._request(token, 'GET', self.url('authorize'),
headers=cookies)
csrf = self.get_csrf(content)
data = {'authorize_access': True,
'csrfmiddlewaretoken': csrf,
'oauth_token': token.key}
resp, content = self._request(token, 'POST', self.url('authorize'),
headers=cookies, data=data,
oauth_callback=callback)

assert resp.status == 302, 'Status was: %s' % resp.status
qsl = parse_qsl(resp['location'][len(callback) + 1:])
verifier = dict(qsl)['oauth_verifier']
token.set_verifier(verifier)

# We have now authorized the app for this user.
resp, content = self._request(token, 'GET', self.url('access_token'))
access_token = dict(parse_qsl(content))
self.data['access_token'] = access_token
self.save_storage()
# Done. Wasn't that fun?

def get_params(self):
return dict(oauth_consumer_key=self.data['consumer_key'],
oauth_nonce=oauth.generate_nonce(),
oauth_signature_method='HMAC-SHA1',
oauth_timestamp=int(time.time()),
oauth_version='1.0')

def _send(self, url, method, data):
resp, content = self._request(None, method, url,
data=data)
if resp.status != 200:
raise ValueError('%s: %s' % (resp.status, content))
try:
return json.loads(content)
except ValueError:
return content

def get_user(self):
return self._send(self.url('user'), 'GET', {})

def create_addon(self, data):
return self._send(self.url('addon'), 'POST', data)

def update_addon(self, data):
return self._send(self.url('addon'), 'PUT', data)

def create_perf(self, data):
return self._send(self.url('perf'), 'POST', data)


if __name__ == '__main__':
username = 'amckay@mozilla.com'
amo = AMOOAuth(domain="addons.mozilla.local", port=8000, protocol='http')
amo.set_consumer(consumer_key='CmAn9KhXR8SD3xUSrf',
consumer_secret='4hPsAW9yCecr4KRSR4DVKanCkgpqDETm')
if not amo.has_access_token():
# This is an example, don't get too excited.
amo.authenticate(username=username)
print amo.get_user()

0 comments on commit 6eeaa28

Please sign in to comment.