Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
Add shared-secret authorization for fireplace (bug 847672)
Browse files Browse the repository at this point in the history
  • Loading branch information
Allen Short committed Mar 27, 2013
1 parent 94c6068 commit b437348
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 15 deletions.
6 changes: 6 additions & 0 deletions docs/topics/install-zamboni/settings-changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Settings Changelog
==================

2012-03-28
----------

* Added ``FIREPLACE_SECRET_KEY`` setting, used for creating shared
secrets for API login from the marketplace frontend.

2012-03-13
----------

Expand Down
15 changes: 15 additions & 0 deletions mkt/account/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import hashlib
import hmac
import uuid
from django.conf import settings

from tastypie import fields, http
Expand Down Expand Up @@ -57,13 +60,25 @@ class Meta:
list_allowed_methods = ['post']
authorization = Authorization()

def get_token(self, email):
unique_id = uuid.uuid4().hex

consumer_id = hashlib.sha1(
email + settings.FIREPLACE_SECRET_KEY).hexdigest()

hm = hmac.new(
unique_id + settings.FIREPLACE_SECRET_KEY,
consumer_id, hashlib.sha512)
return ','.join((email, hm.hexdigest(), unique_id))

def post_list(self, request, **kwargs):
res = browserid_login(
request, browserid_audience=lambda r: settings.FIREPLACE_URL)
if res.status_code == 200:
return self.create_response(
request,
{'error': None,
'token': self.get_token(request.user.email),
'settings': {
'display_name': UserProfile.objects.get(
user=request.user).display_name,
Expand Down
12 changes: 12 additions & 0 deletions mkt/account/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import collections
import json
import uuid

from mock import patch
from nose.tools import eq_
Expand Down Expand Up @@ -98,13 +99,19 @@ def test_patch_other(self):
browserid_url = 'http://firepla.ce:8675/'


class FakeUUID(object):
hex = '000000'


@patch.object(settings, 'FIREPLACE_SECRET_KEY', 'gubbish')
@patch.object(settings, 'FIREPLACE_URL', browserid_url)
class TestLoginHandler(TestCase):
def setUp(self):
super(TestLoginHandler, self).setUp()
self.list_url = get_absolute_url(list_url('login'), api_name='account')
self.create_switch('browserid-login')

@patch.object(uuid, 'uuid4', FakeUUID)
@patch('requests.post')
def test_login_success(self, http_request):
FakeResponse = collections.namedtuple('FakeResponse',
Expand All @@ -116,6 +123,11 @@ def test_login_success(self, http_request):
dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 200)
data = json.loads(res.content)
eq_(data['token'],
'cvan@mozilla.com,95c9063d9f249aacfe5697fc83192ed6480c01463e2a80b3'
'5af5ecaef11754700f4be33818d0e83a0cfc2cab365d60ba53b3c2b9f8f6589d1'
'c43e9bbb876eef0,000000')

@patch('requests.post')
def test_login_failure(self, http_request):
Expand Down
22 changes: 15 additions & 7 deletions mkt/api/authentication.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import hashlib
import hmac
import json
from urlparse import urljoin

Expand Down Expand Up @@ -149,15 +151,21 @@ def is_authenticated(self, request, **kw):
.is_authenticated(request, **kw))


class SessionAuthentication(Authentication):
class SharedSecretAuthentication(Authentication):

def is_authenticated(self, request, **kwargs):
if (request.method == 'GET' and not
request.META.get('HTTP_AUTHORIZATION', None)):
# No OAuth login info. Let's allow read-only
# access based on session cookie.
return not request.user.is_anonymous()
return False
auth = request.COOKIES.get('user')
if not auth:
return False
try:
email, hm, unique_id = auth.split(',')
consumer_id = hashlib.sha1(
email + settings.FIREPLACE_SECRET_KEY).hexdigest()
return hmac.new(unique_id + settings.FIREPLACE_SECRET_KEY,
consumer_id, hashlib.sha512).hexdigest() == hm
except:
log.info('Bad shared-secret auth data: %s', auth)
return False


def initialize_oauth_server_request(request):
Expand Down
4 changes: 2 additions & 2 deletions mkt/api/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
OwnerAuthorization,
OAuthAuthentication,
PermissionAuthorization,
SessionAuthentication)
SharedSecretAuthentication)
from mkt.api.base import MarketplaceModelResource
from mkt.api.forms import (CategoryForm, DeviceTypeForm, NewPackagedForm,
PreviewArgsForm, PreviewJSONForm, StatusForm,
Expand Down Expand Up @@ -384,7 +384,7 @@ class Meta:
detail_allowed_methods = ['get', 'delete']
list_allowed_methods = ['get']
# TODO figure out authentication/authorization soon.
authentication = (OAuthAuthentication(), SessionAuthentication())
authentication = (OAuthAuthentication(), SharedSecretAuthentication())
authorization = Authorization()
fields = ['app', 'user', 'replies', 'rating', 'title',
'body', 'editorreview']
Expand Down
25 changes: 19 additions & 6 deletions mkt/api/tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,27 @@ def test_request_has_role(self):
ok_(self.auth.is_authenticated(self.call()))


class TestSessionAuthentication(TestCase):
@patch.object(settings, 'FIREPLACE_SECRET_KEY', 'gubbish')
class TestSharedSecretAuthentication(TestCase):
fixtures = fixture('user_2519')

def setUp(self):
self.auth = authentication.SessionAuthentication()
self.auth = authentication.SharedSecretAuthentication()
self.profile = UserProfile.objects.get(pk=2519)

def test_session_auth(self):
req = RequestFactory().get('/')
req.user = self.profile.user
req.COOKIES['user'] = ('cfinke@m.com,56b6f1a3dd735d962c56ce7d8f46e02ec'
'1d4748d2c00c407d75f0969d08bb9c68c31b3371aa8130'
'317815c89e5072e31bb94b4121c5c165f3515838d4d6c6'
'0c4,165d631d3c3045458b4516242dad7ae')
ok_(self.auth.is_authenticated(req))

def test_failed_session_auth(self):
req = RequestFactory().get('/')
req.COOKIES['user'] = 'bogus'
ok_(not self.auth.is_authenticated(req))

def test_session_auth_no_post(self):
req = RequestFactory().post('/')
req.user = self.profile.user
Expand All @@ -184,6 +193,7 @@ def test_something(self):


@patch.object(settings, 'SITE_URL', 'http://api/')
@patch.object(settings, 'FIREPLACE_SECRET_KEY', 'gubbish')
class TestMultipleAuthentication(TestCase):
fixtures = fixture('user_2519')

Expand All @@ -193,16 +203,19 @@ def setUp(self):

def test_single(self):
req = RequestFactory().get('/')
req.user = self.profile.user
req.COOKIES['user'] = ('cfinke@m.com,56b6f1a3dd735d962c56ce7d8f46e02ec'
'1d4748d2c00c407d75f0969d08bb9c68c31b3371aa8130'
'317815c89e5072e31bb94b4121c5c165f3515838d4d6c6'
'0c4,165d631d3c3045458b4516242dad7ae')
self.resource._meta.authentication = (
authentication.SessionAuthentication())
authentication.SharedSecretAuthentication())
eq_(self.resource.is_authenticated(req), None)

def test_multiple_passes(self):
req = RequestFactory().get('/')
req.user = AnonymousUser()
self.resource._meta.authentication = (
authentication.SessionAuthentication(),
authentication.SharedSecretAuthentication(),
# Optional auth passes because there are not auth headers.
authentication.OptionalOAuthAuthentication())

Expand Down
3 changes: 3 additions & 0 deletions mkt/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# The origin URL for our Fireplace frontend, from which API requests come.
FIREPLACE_URL = ''

# The key used for generating shared secrets in Fireplace logins.
FIREPLACE_SECRET_KEY = None

ALLOWED_HOSTS += ['.firefox.com']
# We'll soon need a `settings_test_mkt` to override this.
APP_PREVIEW = True
Expand Down

0 comments on commit b437348

Please sign in to comment.