[#8119] U2F multifactor
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 2009515..d997ad3 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-
+import json
import logging
import os
from datetime import datetime, timedelta
@@ -31,6 +31,7 @@
from webob import exc as wexc
from paste.deploy.converters import asbool
from cryptography.hazmat.primitives.twofactor import InvalidToken
+from u2flib_server import u2f_v2 as u2f
import allura.tasks.repo_tasks
from allura import model as M
@@ -807,6 +808,30 @@
tg.flash('Your recovery codes have been regenerated. Save the new codes!')
redirect('/auth/preferences/multifactor_recovery')
+ @expose('jinja:allura:templates/user_u2f.html')
+ @reconfirm_auth
+ @without_trailing_slash
+ def u2f(self, **kwargs):
+ appId = config.get('auth.multifactor.u2f.appId', config['base_url'])
+ register_request = u2f.start_register(appId)
+ session['u2f_reg_req'] = register_request
+ session.save()
+ return dict(
+ appId=appId,
+ register_request=register_request,
+ )
+
+ @expose()
+ @require_post()
+ @reconfirm_auth
+ def u2f_add(self, device_response, **kwargs):
+ register_request = session.pop('u2f_reg_req')
+ session.save()
+ appId = config.get('auth.multifactor.u2f.appId', config['base_url'])
+ registration, cert = u2f.complete_register(register_request, device_response)#, [appId])
+ return
+
+
class UserInfoController(BaseController):
diff --git a/Allura/allura/model/multifactor.py b/Allura/allura/model/multifactor.py
index cf88ce6..1ec9d2b 100644
--- a/Allura/allura/model/multifactor.py
+++ b/Allura/allura/model/multifactor.py
@@ -54,3 +54,6 @@
_id = FieldProperty(S.ObjectId)
user_id = FieldProperty(S.ObjectId, required=True)
code = FieldProperty(str, required=True)
+
+
+# U2F data is stored as user data, since it isn't confidential (just public keys) it doesn't warrant its own collection
\ No newline at end of file
diff --git a/Allura/allura/templates/user_prefs.html b/Allura/allura/templates/user_prefs.html
index c7a8677..0c60b1e 100644
--- a/Allura/allura/templates/user_prefs.html
+++ b/Allura/allura/templates/user_prefs.html
@@ -124,6 +124,7 @@
{% if user_multifactor %}
<p><b class="fa fa-qrcode"></b> <a href="totp_view">View existing configuration</a></p>
<p><b class="fa fa-life-ring"></b> <a href="multifactor_recovery">View recovery codes</a></p>
+ <p><b class="fa fa-key"></b> <a href="u2f">Add or remove U2F hardware keys</a></p>
<form action="multifactor_disable" id="multifactor_disable" method="post">
<p>
<b class="fa fa-trash"></b> <a href="#" class="disable">Disable</a>
diff --git a/Allura/allura/templates/user_u2f.html b/Allura/allura/templates/user_u2f.html
new file mode 100644
index 0000000..40c8ff3
--- /dev/null
+++ b/Allura/allura/templates/user_u2f.html
@@ -0,0 +1,89 @@
+{#-
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-#}
+{% set hide_left_bar = True %}
+{% extends "allura:templates/user_account_base.html" %}
+
+{% block title %}{{c.user.username}} / U2F Setup{% endblock %}
+
+{% block header %}U2F Hardware Key Setup for {{c.user.username}}{% endblock %}
+
+{% block content %}
+ {{ super() }}
+ <div class='grid-20'>
+ <h2>Intro</h2>
+ <p>asdfsadf
+ </p>
+
+ <form method="post" action="u2f_add">
+ <input type="hidden" name="device_response"/>
+ {{ lib.csrf_token() }}
+
+
+ DEBUG: {{ register_request }}
+ </form>
+
+ <p>
+ <a href="/auth/preferences/">Back</a>
+ </p>
+ </div>
+{% endblock %}
+
+{% block extra_css %}
+<style type="text/css">
+</style>
+{% endblock %}
+
+{% block extra_js %}
+
+{# TODO feature detection... u2f already there or chrome?
+
+always need to include one of these I think, Chrome only has low-level APIs,
+ and the function signatures are a little bit different between these and firefox plugin
+<script src="https://demo.yubico.com/js/u2f-api.js"></script>
+#}
+<script src="https://rawgit.com/google/u2f-ref-code/master/u2f-gae-demo/war/js/u2f-api.js"></script>
+
+<script type="text/javascript">
+$(function() {
+ if (window.u2f) {
+ window.u2f.register('{{ appId }}', [
+ {{ h.escape_json(register_request)|safe }}
+ ],
+ [],
+ function (data) {
+ if (data.errorCode) {
+ if (data.errorCode === u2f.ErrorCodes.TIMEOUT) {
+ // FIXME do something to re-prompt
+ $('#messages').notify({message: 'Timeout, please try again.', status:'info'});
+ } else {
+ $('#messages').notify({message: 'An error occurred, please try again.', status:'error'});
+ }
+ console.log(data);
+ return;
+ }
+ var $input = $('input[name=device_response]');
+ $input.val(JSON.stringify(data));
+ $input.closest('form').submit();
+ });
+ }
+});
+
+
+</script>
+{% endblock %}
\ No newline at end of file
diff --git a/Allura/development.ini b/Allura/development.ini
index b160ef6..c059ed3 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -212,6 +212,9 @@
; length of each code. Must be 8 for compatibility with "filesystem-googleauth" files
auth.multifactor.recovery_code.length = 8
+; The default u2f appId will be Allura's base_url
+;auth.multifactor.u2f.appId = https://website.com
+; if testing with HTTP, see https://github.com/google/u2f-ref-code#option-2-use-the-built-in-chrome-support
user_prefs_storage.method = local
; user_prefs_storage.method = ldap
diff --git a/requirements.txt b/requirements.txt
index fc32006..bd46792 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -36,6 +36,7 @@
python-dateutil==1.5
python-magic==0.4.3
python-oembed==0.2.1
+python-u2flib-server==4.0.1
pytz==2014.10
qrcode==5.3
requests==2.0.0