First cut at BrowserID integration [bug 675322]
This commit is contained in:
Родитель
90be6bdea2
Коммит
7ccc374ac8
|
@ -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
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')),
|
||||
)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче