First cut at BrowserID integration [bug 675322]

This commit is contained in:
Allen Short 2011-07-29 14:28:20 -07:00
Родитель 90be6bdea2
Коммит 7ccc374ac8
11 изменённых файлов: 230 добавлений и 5 удалений

Просмотреть файл

@ -1,7 +1,15 @@
{% extends "base.html" %}
{% block title %}{{ page_title(_('User Login')) }}{% endblock %}
{% block js %}{% if form.recaptcha %}{% include("amo/recaptcha_js.html") %}{% endif %}{% endblock %}
{% block js %}
{% if waffle.switch('browserid-login') %}
<script src="https://browserid.org/include.js"></script>
{% endif %}
{% if form.recaptcha %}{% include("amo/recaptcha_js.html") %}{% endif %}
{% endblock %}
{% block content %}
@ -18,6 +26,13 @@
{% endif %}
<h2>{{ _('Log In') }}</h2>
<div class="primary featured">
{% if waffle.switch('browserid-login') %}
<div class="featured-inner">
<a class="browserid-login" href="#" data-url="{{ url('users.browserid_login') }}">
{{ _('Log in with BrowserID') }}
</a>
</div>
{% endif %}
<form method="post" action="" class="featured-inner object-lead user-input">
{{ csrf() }}
<fieldset>

Просмотреть файл

@ -35,6 +35,8 @@ users_patterns = patterns('',
url('^delete$', views.delete, name='users.delete'),
url('^delete_photo$', views.delete_photo, name='users.delete_photo'),
url('^edit$', views.edit, name='users.edit'),
url('^browserid-login', views.browserid_login,
name='users.browserid_login'),
url('^login', views.login, name='users.login'),
url('^logout', views.logout, name='users.logout'),
url('^register$', views.register, name='users.register'),

Просмотреть файл

@ -21,7 +21,7 @@ import waffle
import amo
from amo import messages
from amo.decorators import (json_view, login_required, permission_required,
write)
write, post_required)
from amo.forms import AbuseForm
from amo.urlresolvers import reverse
from amo.utils import send_mail
@ -319,6 +319,20 @@ def _clean_next_url(request):
return request
@anonymous_csrf
@post_required
@ratelimit(block=True, rate=settings.LOGIN_RATELIMIT_ALL_USERS)
def browserid_login(request):
if waffle.switch_is_active('browserid-login'):
logout(request)
user = auth.authenticate(assertion=request.POST['assertion'],
host=request.POST['audience'])
if user is not None:
auth.login(request, user)
return http.HttpResponse(status=200)
return http.HttpResponse(status=401)
@anonymous_csrf
@mobile_template('users/{mobile/}login.html')
@ratelimit(block=True, rate=settings.LOGIN_RATELIMIT_ALL_USERS)

Просмотреть файл

@ -1,6 +1,44 @@
/* Global initialization script */
var z = {};
function makeRedirectAfterBrowserIDLogin(to) {
return function(data, textStatus, jqXHR) {
if (to) {
window.location = to;
}
};
}
function gotVerifiedEmail(assertion, redirectTo, domContext) {
function displayErrBox(errmsg) {
$('.primary', domContext).prepend(
'<div class="notification-box error">'
+ '<ul><h2>' + errmsg + '</h2></ul></div>');
}
if (assertion) {
var a = $.ajax({
url: $('.browserid-login', domContext).attr('data-url'),
type: 'POST',
data: {
'audience': document.location.host,
'assertion': assertion,
'csrfmiddlewaretoken':
$('input[name=csrfmiddlewaretoken]').val()
},
success: makeRedirectAfterBrowserIDLogin(redirectTo),
error: function(jqXHR, textStatus, errorThrown) {
displayErrBox(gettext(
'BrowserID login failed. Maybe you don\'t '
+ 'have an account under that email address?'));}
});
return a;
} else {
// user clicked 'cancel', don't do anything
return null;
};
}
$(document).ready(function(){
// Initialize install buttons.
@ -20,7 +58,18 @@ $(document).ready(function(){
var em = $(this).text().split('').reverse().join('');
$(this).prev('a').attr('href', 'mailto:' + em).addClass('email');
});
// Initialize BrowserID login.
$('.browserid-login').each(
function() {
var to = decodeURIComponent(window.location.href.split('?to=')[1]);
$(this).click(
function (e) {
$('.primary .notification-box').remove();
navigator.id.getVerifiedEmail(
function(assertion) {
gotVerifiedEmail(assertion, to);
});
});});
// fake placeholders if we need to.
if (!('placeholder' in document.createElement('input'))) {
$('input[placeholder]').placeholder();

Просмотреть файл

@ -0,0 +1,54 @@
$(document).ready(function(){
module('login', {
setup: function() {this.sandbox = tests.createSandbox("#browserid-test");},
teardown: function() {this.sandbox.remove();}
});
asyncTest('Login failure (error from server)', function() {
equal($(".primary .notification-box", this.sandbox).length, 0);
function check() {
$.mockjaxClear();
};
redirectAfterBrowserIDLogin = function() { start();};
$.mockjax({url: '/en-US/firefox/users/browserid-login',
response: check,
status: 401});
gotVerifiedEmail("browserid-assertion", "/", this.sandbox).fail(
function() {
equal($(".primary .notification-box h2", this.sandbox).text(),
'BrowserID login failed. Maybe you don\'t have an account'
+ ' under that email address?');
$(".primary .notification-box", this.sandbox).remove();
start();
});
});
test('Login cancellation', function() {
$.mockjax({url: '/en-US/firefox/users/browserid-login',
response: function () {
ok(false, "XHR call made when user cancelled");
}});
equal(gotVerifiedEmail(null, "/", this.sandbox), null);
equal($(".primary .notification-box", this.sandbox).length, 0);
$.mockjaxClear();
});
asyncTest('Login success', function() {
var ajaxCalled = false;
equal($(".primary .notification-box", this.sandbox).length, 0);
makeRedirectAfterBrowserIDLogin = function(to) {
return function() { ajaxCalled = true; $.mockjaxClear();};
};
$.mockjax({url: '/en-US/firefox/users/browserid-login',
response: function () {
return "win";
},
status: 200});
gotVerifiedEmail("browserid-assertion", "/", this.sandbox).done(
function() {
ok(ajaxCalled);
start();
});
});
});

Просмотреть файл

@ -0,0 +1,18 @@
{
"name": "Impala Tests",
"extra_media_urls": [
"js/lib/jstestnet.js",
"js/zamboni/tests.js",
"js/zamboni/global.js",
"js/zamboni/devhub.js",
"js/zamboni/validator.js",
"js/zamboni/l10n.js",
"js/zamboni/storage.js",
"js/zamboni/editors.js",
"js/zamboni/upload.js",
"js/zamboni/files.js",
"js/zamboni/contributions.js",
"js/zamboni/password-strength.js",
"js/impala/persona_creation.js"
]
}

Просмотреть файл

@ -0,0 +1 @@
INSERT INTO waffle_switch (name, active, note) VALUES ('browserid-login', 0, "Support for BrowserID login." ) ON DUPLICATE KEY UPDATE active = 0;

Просмотреть файл

@ -306,6 +306,7 @@ MIDDLEWARE_CLASSES = (
# Auth
AUTHENTICATION_BACKENDS = (
'django_browserid.auth.BrowserIDBackend',
'users.backends.AmoUserBackend',
'cake.backends.SessionBackend',
)
@ -367,6 +368,9 @@ INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.sessions',
# Has to load after auth
'django_browserid',
)
# These apps will be removed from INSTALLED_APPS in a production environment.
@ -921,6 +925,7 @@ CSP_IMG_SRC = ("'self'", STATIC_URL,
)
CSP_SCRIPT_SRC = ("'self'", STATIC_URL,
"https://www.google.com", # Recaptcha
"https://browserid.org",
"https://www.paypalobjects.com",
)
CSP_STYLE_SRC = ("'self'", STATIC_URL,)

Просмотреть файл

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>QUnit Test Suite</title>
<link rel="stylesheet" href="{{ url('qunit_css') }}?v={{ timestamp }}" type="text/css" media="screen">
</head>
<body
data-app="{{ request.APP.short }}"
data-appname="{{ request.APP.pretty }}"
data-appid="{{ request.APP.id }}"
data-min-beta-version="{{ settings.MIN_BETA_VERSION }}"
data-anonymous="true"
data-readonly="false"
data-media-url="{{ MEDIA_URL }}"
data-paypal-url="/paypal"
data-default-locale="en-us">
<h1 id="qunit-header">QUnit Test Suite ({{ suite.name }})</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
{% if in_directory or subsuites %}
<div id="navigation">
{% if in_subdirectory %}
<a href="{{ url('qunit_test_overview', previous_directory) }}">..</a>
{% endif %}
{% for suite in subsuites %}
<a href="{{ suite }}/">{{ suite }}</a>
{% endfor %}
</div>
{% endif %}
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
<div id="sandbox"><!-- custom Zamboni sandbox area --></div>
<div id="browserid-test">
<div class="primary"></div>
<a class="browserid-login" href="#"
data-url="{{ url('users.browserid_login') }}">BrowserID Login</a>
</div>
<script src="{{ static(url('jsi18n')) }}"></script>
{{ js('impala') }}
<script type="text/javascript" src="{{ url('qunit_js') }}?v={{ timestamp }}"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/lib/jquery.mockjax.js?v={{ timestamp }}"></script>
{% for url in suite.extra_urls %}
<script type="text/javascript" src="{{ url }}?v={{ timestamp }}"></script>
{% endfor %}
{% for url in suite.extra_media_urls %}
<script type="text/javascript" src="{{ MEDIA_URL }}{{ url }}?v={{ timestamp }}"></script>
{% endfor %}
{% for file in files %}
<script type="text/javascript" src="{{ url('qunit_test', file) }}?v={{ timestamp }}"></script>
{% endfor %}
</body>
</html>

Просмотреть файл

@ -354,6 +354,7 @@
sample: function () { return true; }
}
</script>
<script src="{{ static(url('jsi18n')) }}"></script>
{{ js('common') }}
<script async defer src="{{ static(url('addons.buttons.js')) }}"></script>

13
urls.py
Просмотреть файл

@ -1,3 +1,4 @@
import os.path
from django.conf import settings
from django.conf.urls.defaults import patterns, url, include
from django.contrib import admin
@ -153,20 +154,28 @@ urlpatterns += patterns('piston.authentication.oauth.views',
if 'django_qunit' in settings.INSTALLED_APPS:
def zamboni_qunit(request, path):
def _zamboni_qunit(request, path, template):
from time import time
import django_qunit.views
import jingo
ctx = django_qunit.views.get_suite_context(request, path)
ctx.update(timestamp=time())
response = jingo.render(request, 'qunit.html', ctx)
response = jingo.render(request, template, ctx)
# This allows another site to embed the QUnit suite
# in an iframe (for CI).
response['x-frame-options'] = ''
return response
def zamboni_qunit(request, path):
return _zamboni_qunit(request, path, 'qunit.html')
def zamboni_impala_qunit(request, path):
return _zamboni_qunit(request, os.path.join(path, 'impala/'),
'impala_qunit.html')
urlpatterns += patterns('',
url(r'^qunit/(?P<path>.*)', zamboni_qunit),
url(r'^impala-qunit/(?P<path>.*)', zamboni_impala_qunit),
url(r'^_qunit/', include('django_qunit.urls')),
)