Commit 810b9f6a authored by Christophe Maudoux's avatar Christophe Maudoux 🐛

WIP - 2ndFA manager module

parent 703abd5c
......@@ -77,6 +77,30 @@ sub deleteU2FKey {
return $self->sendJSONresponse( $req, { result => 1 } );
}
sub deleteTOTPKey {
my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { result => 1 } )
if ( $self->{demoMode} );
my $mod = $self->getMod($req)
or return $self->sendError( $req, undef, 400 );
my $id = $req->params('sessionId')
or return $self->sendError( $req, 'sessionId is missing', 400 );
# Try to read session
my $session = $self->getApacheSession( $mod, $id )
or return $self->sendError( $req, undef, 400 );
# Delete U2F key attributs and update session
$session->data->{_totp2fSecret} = '';
$session->update( \%{ $session->data } );
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
if ( $session->error ) {
return $self->sendError( $req, $session->error, 200 );
}
return $self->sendJSONresponse( $req, { result => 1 } );
}
sub addU2FKey {
my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { result => 1 } )
......
......@@ -91,7 +91,7 @@ sub init {
# Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp
my $linksIcons =
{ 'conf' => 'cog', 'sessions' => 'duplicate', 'notifications' => 'bell', 'SFA' => 'wrench' };
{ 'conf' => 'cog', 'sessions' => 'duplicate', 'notifications' => 'bell', '2ndFA' => 'wrench' };
$self->links( [] );
for ( my $i = 0 ; $i < @links ; $i++ ) {
......
package Lemonldap::NG::Manager::SFA;
package Lemonldap::NG::Manager::2ndFA;
use 5.10.0;
use utf8;
......@@ -37,21 +37,21 @@ sub addRoutes {
['GET']
)
# DELETE U2F KEY
# DELETE 2FA KEY
->addRoute(
sfa => { ':sessionType' => { ':sessionId' => 'deleteU2FKey' } },
sfa => { ':sessionType' => { ':sessionId' => 'delete2FAKey' } },
['DELETE']
)
# ADD U2F KEY
# ADD 2FA KEY
->addRoute(
sfa => { ':sessionType' => { ':sessionId' => 'registerU2FKey' } },
sfa => { ':sessionType' => { ':sessionId' => 'add2FAKey' } },
['PUT']
)
# VERIFY U2F KEY
# VERIFY 2FA KEY
->addRoute(
sfa => { ':sessionType' => { ':sessionId' => 'verifyU2FKey' } },
sfa => { ':sessionType' => { ':sessionId' => 'verify2FAKey' } },
['POST']
);
......@@ -63,23 +63,51 @@ sub addRoutes {
}
############################
# II. REGISTRATION METHODS #
############################
###################
# II. 2FA METHODS #
###################
sub registerU2FKey {
sub delete2FAKey {
my ( $self, $req, $session, $skey ) = @_;
my $mod = $self->getMod($req)
or return $self->sendError( $req, undef, 400 );
my $params = $req->parameters();
my $Key = $params->{Key};
if ( $Key =~ /\bU2F\b/ ) {
return $self->deleteU2FKey( $req, $session, $skey );
}
elsif ( $Key =~ /\bTOTP\b/ ) {
return $self->deleteTOTPKey( $req, $session, $skey );
}
else {
return $self->sendError( $req, undef, 666 );
}
}
sub add2FAKey {
my ( $self, $req, $session, $skey ) = @_;
eval 'use Crypt::U2F::Server::Simple';
if ($@) {
$self->error("Can't load U2F library: $@");
return 0;
}
return $self->addU2FKey( $req, $session, $skey );
}
sub verify2FAKey {
my ( $self, $req, $session, $skey ) = @_;
return $self->addU2FKey( $req, $session, $skey );
}
########################
# III. DISPLAY METHODS #
......@@ -90,14 +118,16 @@ sub sfa {
# Case 1: only one session is required
if ($session) {
$self->error("Can't load U2F library: $session");
return $self->session( $req, $session, $skey );
}
my $mod = $self->getMod($req)
or return $self->sendError( $req, undef, 400 );
my $params = $req->parameters();
my $type = delete $params->{sessionType};
$type = ucfirst($type);
my $Key = $params->{sessionType};
my $res;
......@@ -106,7 +136,7 @@ sub sfa {
my $whatToTrace = Lemonldap::NG::Handler::PSGI::Main->tsv->{whatToTrace};
# 2.1 Get fields to require
my @fields = ( '_httpSessionType', $self->{ipField}, $whatToTrace, '_u2fKeyHandle' );
my @fields = ( '_httpSessionType', $self->{ipField}, $whatToTrace, '_u2fKeyHandle', '_totp2fSecret' );
if ( my $groupBy = $params->{groupBy} ) {
$groupBy =~ s/^substr\((\w+)(?:,\d+(?:,\d+)?)?\)$/$1/;
$groupBy =~ s/^_whatToTrace$/$whatToTrace/o
......@@ -195,7 +225,8 @@ sub sfa {
# Display sessions with registered U2F key only
foreach my $session ( keys %$res ) {
delete $res->{$session}
unless ( defined $res->{$session}->{_u2fKeyHandle} and length $res->{$session}->{_u2fKeyHandle} )
unless ( ( defined $res->{$session}->{_u2fKeyHandle} and length $res->{$session}->{_u2fKeyHandle} )
or ( defined $res->{$session}->{_totp2fSecret} and length $res->{$session}->{_totp2fSecret} ) )
}
my $total = ( keys %$res );
......
......@@ -2,6 +2,26 @@
# Session explorer
###
setMsg = (msg, level) ->
$('#msg').html window.translate msg
$('#color').removeClass 'message-positive message-warning alert-success alert-warning'
$('#color').addClass "message-#{level}"
level = 'success' if level == 'positive'
$('#color').addClass "alert-#{level}"
displayError = (j, status, err) ->
console.log 'Error', err
res = JSON.parse j.responseText
if res and res.error
res = res.error.replace /.* /, ''
console.log 'Returned error', res
setMsg res, 'warning'
# Max number of session to display (see overScheme)
max = 25
......@@ -16,23 +36,6 @@ schemes =
(t,v) ->
"#{t}=#{v}"
]
#~ ipAddr: [
#~ (t,v) ->
#~ "groupBy=net(#{t},16,1)"
#~ (t,v) ->
#~ v = v + '.' unless v.match /:/
#~ "#{t}=#{v}*&groupBy=net(#{t},32,2)"
#~ (t,v) ->
#~ v = v + '.' unless v.match /:/
#~ "#{t}=#{v}*&groupBy=net(#{t},48,3)"
#~ (t,v) ->
#~ v = v + '.' unless v.match /:/
#~ "#{t}=#{v}*&groupBy=net(#{t},128,4)"
#~ (t,v) ->
#~ "#{t}=#{v}&groupBy=_whatToTrace"
#~ (t,v,q) ->
#~ q.replace(/\&groupBy.*$/, '') + "&_whatToTrace=#{v}"
#~ ]
_startTime: [
(t,v) ->
"groupBy=substr(#{t},8)"
......@@ -50,14 +53,6 @@ schemes =
console.log q
q.replace(/\&groupBy.*$/, '') + "&_whatToTrace=#{v}"
]
#doubleIp: [
#(t,v) ->
#t
#(t,v) ->
#"_whatToTrace=#{v}&groupBy=ipAddr"
#(t,v,q) ->
#q.replace(/\&groupBy.*$/, '') + "&ipAddr=#{v}"
#]
overScheme =
_whatToTrace: (t,v,level,over) ->
......@@ -71,9 +66,7 @@ overScheme =
else
null
#hiddenAttributes = '_password _u2fKeyHandle _u2fUserKey _totp2fSecret'
hiddenAttributes = '_password _u2fKeyHandle _u2fUserKey _totp2fSecret'
hiddenAttributes = '_password'
# Attributes to group in session display
categories =
......@@ -105,6 +98,14 @@ menu =
title: 'deleteTOTPKey'
icon: 'trash'
]
addTOTPKey: [
title: 'addTOTPKey'
icon: 'plus'
]
verifyTOTPKey: [
title: 'verifyTOTPKey'
icon: 'check'
]
home: []
###
......@@ -151,11 +152,10 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
$scope.showM = false
# SESSION MANAGEMENT
# Delete U2F key
$scope.deleteU2FKey = ->
$scope.waiting = true
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}/U2F").then (response) ->
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
......@@ -168,7 +168,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
# Delete TOTP key
$scope.deleteTOTPKey = ->
$scope.waiting = true
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}/TOTP").then (response) ->
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
......@@ -181,34 +181,54 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
# Add U2F key
$scope.addU2FKey = ->
$scope.waiting = true
$http['put']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
#$scope.currentSession = null
#$scope.currentScope.remove()
$http['put']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
#$scope.currentSession = null
#$scope.currentScope.remove()
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = false
$http.get("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
$scope.currentSession = transformSession response.data
# Add TOTP key
$scope.addTOTPKey = ->
$scope.waiting = true
$http['put']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = false
# Verify U2F key
$scope.verifyU2FKey = ->
$scope.waiting = true
$http['post']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
#$scope.currentSession = null
#$scope.currentScope.remove()
$http['post']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
#$scope.currentSession = null
#$scope.currentScope.remove()
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = true
# Verify TOTP key
$scope.verifyTOTPKey = ->
$scope.waiting = true
$http['post']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = true
$http.get("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
$scope.currentSession = transformSession response.data
$scope.showT = false
# Open node
$scope.stoggle = (scope) ->
......@@ -256,6 +276,9 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
session[key] = $scope.localeDate value
else if key.match /^(_startTime|_updateTime)$/
session[key] = _stToStr value
else if key.match /^(_u2fKeyHandle|_u2fUserKey|_totp2fSecret)$/
session[key] = '##########'
res = []
# 2. Push session keys in result, grouped by categories
......@@ -432,4 +455,48 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
# Default to '_whatToTrace'
c = $location.path().match /^\/(\w+)/
$scope.type = if c then c[1] else '_whatToTrace'
# Unregistration function (launched by "unregister" button)
u2fcheck = ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.showT = false
$.ajax
type: "GET"
url: "https://manager.example.com:19876/sfa.html?U2FCheck=1"
data: {}
dataType: 'json'
error: displayError
success: (resp) ->
if resp.error
setMsg 'u2fFailed', 'warning'
else if resp.result
setMsg 'u2fUnregistered', 'positive'
error: displayError
totpcheck = ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.showT = false
$.ajax
type: "GET"
url: "https://manager.example.com:19876/sfa.html?U2FCheck=1"
data: {}
dataType: 'json'
error: displayError
success: (resp) ->
if resp.error
setMsg 'u2fFailed', 'warning'
else if resp.result
setMsg 'u2fUnregistered', 'positive'
error: displayError
# CheckBox "click" events
$(document).ready ->
$('#U2FCheck').on 'click', u2fcheck
$('#TOTPCheck').on 'click', totpcheck
]
......@@ -5,7 +5,28 @@
*/
(function() {
var categories, hiddenAttributes, llapp, max, menu, overScheme, schemes;
var categories, displayError, hiddenAttributes, llapp, max, menu, overScheme, schemes, setMsg;
setMsg = function(msg, level) {
$('#msg').html(window.translate(msg));
$('#color').removeClass('message-positive message-warning alert-success alert-warning');
$('#color').addClass("message-" + level);
if (level === 'positive') {
level = 'success';
}
return $('#color').addClass("alert-" + level);
};
displayError = function(j, status, err) {
var res;
console.log('Error', err);
res = JSON.parse(j.responseText);
if (res && res.error) {
res = res.error.replace(/.* /, '');
console.log('Returned error', res);
return setMsg(res, 'warning');
}
};
max = 25;
......@@ -19,30 +40,6 @@
return t + "=" + v;
}
],
ipAddr: [
function(t, v) {
return "groupBy=net(" + t + ",16,1)";
}, function(t, v) {
if (!v.match(/:/)) {
v = v + '.';
}
return t + "=" + v + "*&groupBy=net(" + t + ",32,2)";
}, function(t, v) {
if (!v.match(/:/)) {
v = v + '.';
}
return t + "=" + v + "*&groupBy=net(" + t + ",48,3)";
}, function(t, v) {
if (!v.match(/:/)) {
v = v + '.';
}
return t + "=" + v + "*&groupBy=net(" + t + ",128,4)";
}, function(t, v) {
return t + "=" + v + "&groupBy=_whatToTrace";
}, function(t, v, q) {
return q.replace(/\&groupBy.*$/, '') + ("&_whatToTrace=" + v);
}
],
_startTime: [
function(t, v) {
return "groupBy=substr(" + t + ",8)";
......@@ -60,15 +57,6 @@
console.log(q);
return q.replace(/\&groupBy.*$/, '') + ("&_whatToTrace=" + v);
}
],
doubleIp: [
function(t, v) {
return t;
}, function(t, v) {
return "_whatToTrace=" + v + "&groupBy=ipAddr";
}, function(t, v, q) {
return q.replace(/\&groupBy.*$/, '') + ("&ipAddr=" + v);
}
]
};
......@@ -122,6 +110,24 @@
icon: 'check'
}
],
delTOTPKey: [
{
title: 'deleteTOTPKey',
icon: 'trash'
}
],
addTOTPKey: [
{
title: 'addTOTPKey',
icon: 'plus'
}
],
verifyTOTPKey: [
{
title: 'verifyTOTPKey',
icon: 'check'
}
],
home: []
};
......@@ -134,7 +140,7 @@
llapp.controller('SessionsExplorerCtrl', [
'$scope', '$translator', '$location', '$q', '$http', function($scope, $translator, $location, $q, $http) {
var autoId, c, pathEvent, sessionType;
var autoId, c, pathEvent, sessionType, totpcheck, u2fcheck;
$scope.links = links;
$scope.menulinks = menulinks;
$scope.staticPrefix = staticPrefix;
......@@ -176,7 +182,20 @@
};
$scope.deleteU2FKey = function() {
$scope.waiting = true;
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = false;
};
$scope.deleteTOTPKey = function() {
$scope.waiting = true;
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=TOTP").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
......@@ -189,29 +208,55 @@
};
$scope.addU2FKey = function() {
$scope.waiting = true;
$http['put'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
$http['put'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
$scope.showT = false;
$http.get(scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
return $scope.currentSession = transformSession(response.data);
return $scope.showT = false;
};
$scope.addTOTPKey = function() {
$scope.waiting = true;
$http['put'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=TOTP").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = false;
};
$scope.verifyU2FKey = function() {
$scope.waiting = true;
$http['post'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
$http['post'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
$scope.showT = true;
$http.get(scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
return $scope.currentSession = transformSession(response.data);
return $scope.showT = true;
};
$scope.verifyTOTPKey = function() {
$scope.waiting = true;
$http['post'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=TOTP").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = false;
return $scope.showT = true;
};
$scope.stoggle = function(scope) {
var node;
......@@ -224,7 +269,7 @@
$scope.displaySession = function(scope) {
var sessionId, transformSession;
transformSession = function(session) {
var _insert, _stToStr, attr, attrs, category, i, id, j, k, key, l, len, len1, len2, ref, ref1, res, subres, time, tmp, value;
var _insert, _stToStr, attr, attrs, category, i, id, k, key, l, len, len1, len2, m, ref, ref1, res, subres, time, tmp, value;
_stToStr = function(s) {
return s;
};
......@@ -266,6 +311,8 @@
session[key] = $scope.localeDate(value);
} else if (key.match(/^(_startTime|_updateTime)$/