Commit a1a96f68 authored by Xavier Guimard's avatar Xavier Guimard

Add 2F registration engine (#1148 #1359 #1391)

parent bbf6d9f0
......@@ -86,6 +86,15 @@ sub init {
$self->addUnauthRoute( '2fchoice' => 'redirect', ['GET'] );
}
# Enable 2F registration URL only if a least 1 registration module
# is enabled
if ( @{ $self->{sfRModules} } ) {
# Registration base
$self->addAuthRoute( '2fregisters' => 'displayRegister', ['GET'] );
$self->addAuthRoute( '2fregisters' => 'register', ['POST'] );
}
return 1;
}
......@@ -197,4 +206,75 @@ sub _redirect {
];
}
sub displayRegister {
my ( $self, $req, $tpl ) = @_;
# After verifying rule:
# - display template if $tpl
# - else display choice template
if ($tpl) {
my ($m) = grep { $_->{m}->prefix eq $tpl } @{ $self->sfRModules };
unless ($m) {
return $self->p->sendError( $req, 'Unexistent register module',
400 );
}
unless ( $m->{r}->( $req, $req->userData ) ) {
return $self->p->sendError( $req, 'Registration unauthorizated',
403 );
}
return $self->p->sendHtml( $req, $m->{m}->template );
}
# If only one 2F is available, redirect to it
my @am;
foreach my $m ( @{ $self->sfRModules } ) {
$self->logger->debug(
'Looking if ' . $m->{m}->prefix . '2F register is available' );
if ( $m->{r}->( $req, $req->userData ) ) {
push @am, '/2fregisters/' . $m->{m}->prefix;
}
}
if ( @am == 1 ) {
return [ 302, [ Location => $self->conf->{portal} . $am[0] ], [] ];
}
return $self->p->sendHtml( $req, '2fregisters' );
}
# Check rule and display
sub register {
my ( $self, $req, $tpl, @args ) = @_;
# After verifying rule:
# - call register run method if $tpl
# - else give JSON list of available registers for this user
if ($tpl) {
my ($m) = grep { $_->{m}->prefix eq $tpl } @{ $self->sfRModules };
unless ($m) {
return $self->p->sendError( $req, 'Unexistent register module',
400 );
}
unless ( $m->{r}->( $req, $req->userData ) ) {
$self->userLogger->error("$tpl 2F registration refused");
return $self->p->sendError( $req, 'Registration refused', 403 );
}
return $m->{m}->run( $req, @args );
}
my @am;
foreach my $m ( @{ $self->sfRModules } ) {
$self->logger->debug(
'Looking if ' . $m->{m}->prefix . '2F register is available' );
if ( $m->{r}->( $req, $req->userData ) ) {
$self->logger->debug(' -> OK');
my $name = $m->{m}->prefix;
push @am,
{
name => $name,
logo => $m->{m}->logo,
url => "/2fregisters/$name"
};
}
}
return $self->p->sendJSONresponse( $req, \@am );
}
1;
......@@ -12,6 +12,8 @@ extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Common::TOTP';
has prefix => ( is => 'rw', default => 'totp' );
has template => ( is => 'ro', default => 'totpregister' );
has ott => (
is => 'rw',
lazy => 1,
......@@ -24,18 +26,11 @@ has ott => (
);
sub init {
my ($self) = @_;
$self->addAuthRoute(
totpregister => { ':action' => 'selfRegister' },
['POST']
);
$self->addAuthRoute( 'totpregister.html' => undef, ['GET'] );
return 1;
}
sub selfRegister {
my ( $self, $req ) = @_;
my $action = $req->param('action');
sub run {
my ( $self, $req, $action ) = @_;
my $user = $req->userData->{ $self->conf->{whatToTrace} };
unless ($user) {
return $self->p->sendError( $req,
......
......@@ -12,11 +12,11 @@ extends 'Lemonldap::NG::Portal::Lib::U2F';
has prefix => ( is => 'rw', default => 'u' );
has template => ( is => 'ro', default => 'u2fregister' );
sub init {
my ($self) = @_;
return 0 unless $self->SUPER::init;
$self->addAuthRoute( u2fregister => { ':action' => 'run' }, ['POST'] );
$self->addAuthRoute( 'u2fregister.html' => undef, ['GET'] );
return 1;
}
......@@ -24,8 +24,7 @@ sub init {
# Main method
sub run {
my ( $self, $req ) = @_;
my $action = $req->param('action');
my ( $self, $req, $action ) = @_;
if ( $action eq 'register' ) {
my $challenge = $self->crypter->registrationChallenge;
......
......@@ -201,6 +201,11 @@ sub reloadConf {
}
$self->conf->{domain} =~ s/^([^\.])/.$1/;
# Load menu
# ---------
$self->menu( $self->loadPlugin('::Main::Menu') );
$self->displayInit;
# Load authentication/userDB
# --------------------------
my $mod;
......@@ -294,9 +299,6 @@ sub reloadConf {
or $self->logger->error(
'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error );
$self->menu( $self->loadPlugin('::Main::Menu') );
$self->displayInit;
# Load plugins
foreach my $plugin ( $self->enabledPlugins ) {
$self->loadPlugin($plugin) or return $self->fail;
......
......@@ -22,13 +22,16 @@ token=''
getKey = (reset) ->
$.ajax
type: "POST",
url: "#{portal}/totpregister/getkey"
url: "#{portal}/2fregisters/totp/getkey"
dataType: 'json'
data:
newkey: reset
error: displayError
# Display key and QR code
success: (data) ->
unless data.portal and data.user and data.secret
return setMsg 'PE24', 'danger'
# Generate OTP url
s = "otpauth://totp/#{escape(data.portal)}:#{escape(data.user)}?secret=#{data.secret}&issuer=#{escape(data.portal)}"
if data.digits != 6
......@@ -56,7 +59,7 @@ verify = ->
else
$.ajax
type: "POST",
url: "#{portal}/totpregister/verify"
url: "#{portal}/2fregisters/totp/verify"
dataType: 'json'
data:
token: token
......
......@@ -22,7 +22,7 @@ register = ->
# 1 get registration token
$.ajax
type: "POST",
url: "#{portal}u2fregister/register"
url: "#{portal}2fregisters/u/register"
data: {}
dataType: 'json'
error: displayError
......@@ -43,7 +43,7 @@ register = ->
# 3 send response
$.ajax
type: "POST"
url: "#{portal}u2fregister/registration"
url: "#{portal}2fregisters/u/registration"
data:
registration: JSON.stringify data
challenge: JSON.stringify ch
......@@ -59,7 +59,7 @@ register = ->
unregister = ->
$.ajax
type: "POST"
url: "#{portal}u2fregister/unregistration"
url: "#{portal}2fregisters/u/unregistration"
data: {}
dataType: 'json'
error: displayError
......@@ -75,7 +75,7 @@ verify = ->
# 1 get challenge
$.ajax
type: "POST",
url: "#{portal}u2fregister/verify"
url: "#{portal}2fregisters/u/verify"
data: {}
dataType: 'json'
error: displayError
......@@ -94,7 +94,7 @@ verify = ->
# 3 send response
$.ajax
type: "POST"
url: "#{portal}u2fregister/signature"
url: "#{portal}2fregisters/u/signature"
data:
signature: JSON.stringify data
dataType: 'json'
......
// Generated by CoffeeScript 1.9.3
// Generated by CoffeeScript 1.12.7
/*
LemonLDAP::NG TOTP registration script
......@@ -33,7 +33,7 @@ LemonLDAP::NG TOTP registration script
getKey = function(reset) {
return $.ajax({
type: "POST",
url: portal + "/totpregister/getkey",
url: portal + "/2fregisters/totp/getkey",
dataType: 'json',
data: {
newkey: reset
......@@ -41,6 +41,9 @@ LemonLDAP::NG TOTP registration script
error: displayError,
success: function(data) {
var qr, s;
if (!(data.portal && data.user && data.secret)) {
return setMsg('PE24', 'danger');
}
s = "otpauth://totp/" + (escape(data.portal)) + ":" + (escape(data.user)) + "?secret=" + data.secret + "&issuer=" + (escape(data.portal));
if (data.digits !== 6) {
s += "&digits=" + data.digits;
......@@ -72,7 +75,7 @@ LemonLDAP::NG TOTP registration script
} else {
return $.ajax({
type: "POST",
url: portal + "/totpregister/verify",
url: portal + "/2fregisters/totp/verify",
dataType: 'json',
data: {
token: token,
......
(function(){var a,b,e,d,c,f;e=function(g,h){$("#msg").html(window.translate(g));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+h);if(h==="positive"){h="success"}return $("#color").addClass("alert-"+h)};a=function(h,g,k){var i;console.log("Error",k);i=JSON.parse(h.responseText);if(i&&i.error){i=i.error.replace(/.* /,"");console.log("Returned error",i);return e(i,"warning")}};d="";b=function(g){return $.ajax({type:"POST",url:portal+"/totpregister/getkey",dataType:"json",data:{newkey:g},error:a,success:function(j){var h,i;i="otpauth://totp/"+(escape(j.portal))+":"+(escape(j.user))+"?secret="+j.secret+"&issuer="+(escape(j.portal));if(j.digits!==6){i+="&digits="+j.digits}if(j.interval!==30){i+="&period="+j.interval}h=new QRious({element:document.getElementById("qr"),value:i,size:150});$("#serialized").text(i);if(j.newkey){e("yourNewTotpKey","warning")}else{e("yourTotpKey","success")}return d=j.token}})};f=function(){var g;g=$("#code").val();if(!g){return e("fillTheForm","warning")}else{return $.ajax({type:"POST",url:portal+"/totpregister/verify",dataType:"json",data:{token:d,code:g},error:a,success:function(h){if(h.error){if(h.error.match(/badCode/)){return e("badCode","warning")}else{return e(h.error,"danger")}}else{return e("yourKeyIsRegistered","success")}}})}};c=function(){return $.ajax({type:"POST",url:portal+"/totpregister/unregister",data:{},dataType:"json",error:a,success:function(g){}},e("yourKeyIsUnregistered","success"))};$(document).ready(function(){b(0);$("#changekey").on("click",function(){return b(1)});$("#verify").on("click",function(){return f()});return $("#unregister").on("click",function(){return c()})})}).call(this);
\ No newline at end of file
(function(){var a,b,e,d,c,f;e=function(g,h){$("#msg").html(window.translate(g));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+h);if(h==="positive"){h="success"}return $("#color").addClass("alert-"+h)};a=function(h,g,k){var i;console.log("Error",k);i=JSON.parse(h.responseText);if(i&&i.error){i=i.error.replace(/.* /,"");console.log("Returned error",i);return e(i,"warning")}};d="";b=function(g){return $.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",data:{newkey:g},error:a,success:function(j){var h,i;if(!(j.portal&&j.user&&j.secret)){return e("PE24","danger")}i="otpauth://totp/"+(escape(j.portal))+":"+(escape(j.user))+"?secret="+j.secret+"&issuer="+(escape(j.portal));if(j.digits!==6){i+="&digits="+j.digits}if(j.interval!==30){i+="&period="+j.interval}h=new QRious({element:document.getElementById("qr"),value:i,size:150});$("#serialized").text(i);if(j.newkey){e("yourNewTotpKey","warning")}else{e("yourTotpKey","success")}return d=j.token}})};f=function(){var g;g=$("#code").val();if(!g){return e("fillTheForm","warning")}else{return $.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:d,code:g},error:a,success:function(h){if(h.error){if(h.error.match(/badCode/)){return e("badCode","warning")}else{return e(h.error,"danger")}}else{return e("yourKeyIsRegistered","success")}}})}};c=function(){return $.ajax({type:"POST",url:portal+"/totpregister/unregister",data:{},dataType:"json",error:a,success:function(g){}},e("yourKeyIsUnregistered","success"))};$(document).ready(function(){b(0);$("#changekey").on("click",function(){return b(1)});$("#verify").on("click",function(){return f()});return $("#unregister").on("click",function(){return c()})})}).call(this);
\ No newline at end of file
// Generated by CoffeeScript 1.9.3
// Generated by CoffeeScript 1.12.7
/*
LemonLDAP::NG U2F registration script
......@@ -31,7 +31,7 @@ LemonLDAP::NG U2F registration script
register = function() {
return $.ajax({
type: "POST",
url: portal + "u2fregister/register",
url: portal + "2fregisters/u/register",
data: {},
dataType: 'json',
error: displayError,
......@@ -52,7 +52,7 @@ LemonLDAP::NG U2F registration script
} else {
return $.ajax({
type: "POST",
url: portal + "u2fregister/registration",
url: portal + "2fregisters/u/registration",
data: {
registration: JSON.stringify(data),
challenge: JSON.stringify(ch)
......@@ -76,7 +76,7 @@ LemonLDAP::NG U2F registration script
unregister = function() {
return $.ajax({
type: "POST",
url: portal + "u2fregister/unregistration",
url: portal + "2fregisters/u/unregistration",
data: {},
dataType: 'json',
error: displayError,
......@@ -94,7 +94,7 @@ LemonLDAP::NG U2F registration script
verify = function() {
return $.ajax({
type: "POST",
url: portal + "u2fregister/verify",
url: portal + "2fregisters/u/verify",
data: {},
dataType: 'json',
error: displayError,
......@@ -113,7 +113,7 @@ LemonLDAP::NG U2F registration script
} else {
return $.ajax({
type: "POST",
url: portal + "u2fregister/signature",
url: portal + "2fregisters/u/signature",
data: {
signature: JSON.stringify(data)
},
......
(function(){var a,c,d,b,e;d=function(f,g){$("#msg").html(window.translate(f));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+g);if(g==="positive"){g="success"}return $("#color").addClass("alert-"+g)};a=function(g,f,i){var h;console.log("Error",i);h=JSON.parse(g.responseText);if(h&&h.error){h=h.error.replace(/.* /,"");console.log("Returned error",h);return d(h,"warning")}};c=function(){return $.ajax({type:"POST",url:portal+"u2fregister/register",data:{},dataType:"json",error:a,success:function(f){var g;g=[{challenge:f.challenge,version:f.version}];d("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(f.appId,g,[],function(h){$("#u2fPermission").hide();if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/registration",data:{registration:JSON.stringify(h),challenge:JSON.stringify(f)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fRegistered","positive")}}},error:a})}})}})};b=function(){return $.ajax({type:"POST",url:portal+"u2fregister/unregistration",data:{},dataType:"json",error:a,success:function(f){if(f.error){return d("u2fFailed","warning")}else{if(f.result){return d("u2fUnregistered","positive")}}},error:a})};e=function(){return $.ajax({type:"POST",url:portal+"u2fregister/verify",data:{},dataType:"json",error:a,success:function(f){var g;g=[{keyHandle:f.keyHandle,version:f.version}];d("touchU2fDevice","positive");return u2f.sign(f.appId,f.challenge,g,function(h){if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/signature",data:{signature:JSON.stringify(h)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fSuccess","positive")}}},error:function(k,i,l){return console.log("error",l)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",c);$("#unregister").on("click",b);$("#verify").on("click",e);return $("#goback").attr("href",portal)})}).call(this);
\ No newline at end of file
(function(){var a,c,d,b,e;d=function(f,g){$("#msg").html(window.translate(f));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+g);if(g==="positive"){g="success"}return $("#color").addClass("alert-"+g)};a=function(g,f,i){var h;console.log("Error",i);h=JSON.parse(g.responseText);if(h&&h.error){h=h.error.replace(/.* /,"");console.log("Returned error",h);return d(h,"warning")}};c=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/register",data:{},dataType:"json",error:a,success:function(f){var g;g=[{challenge:f.challenge,version:f.version}];d("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(f.appId,g,[],function(h){$("#u2fPermission").hide();if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/registration",data:{registration:JSON.stringify(h),challenge:JSON.stringify(f)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fRegistered","positive")}}},error:a})}})}})};b=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/unregistration",data:{},dataType:"json",error:a,success:function(f){if(f.error){return d("u2fFailed","warning")}else{if(f.result){return d("u2fUnregistered","positive")}}},error:a})};e=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/verify",data:{},dataType:"json",error:a,success:function(f){var g;g=[{keyHandle:f.keyHandle,version:f.version}];d("touchU2fDevice","positive");return u2f.sign(f.appId,f.challenge,g,function(h){if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/signature",data:{signature:JSON.stringify(h)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fSuccess","positive")}}},error:function(k,i,l){return console.log("error",l)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",c);$("#unregister").on("click",b);$("#verify").on("click",e);return $("#goback").attr("href",portal)})}).call(this);
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment