More work on two-factor support, including new API for provider UI

This commit is contained in:
Jamie Cameron
2013-09-29 12:25:20 -07:00
parent 6813862667
commit 4ec798e1bc
10 changed files with 105 additions and 39 deletions

View File

@@ -75,6 +75,7 @@ while(<PWFILE>) {
$user{'temppass'} = int($user[10]);
$user{'twofactor_provider'} = $user[11];
$user{'twofactor_id'} = $user[12];
$user{'twofactor_apikey'} = $user[13];
$user{'modules'} = $acl{$user[0]};
$user{'lang'} = $gconfig{"lang_$user[0]"};
$user{'notabs'} = $gconfig{"notabs_$user[0]"};
@@ -464,7 +465,8 @@ else {
$user{'nochange'},":",
$user{'temppass'},":",
$user{'twofactor_provider'},":",
$user{'twofactor_id'},
$user{'twofactor_id'},":",
$user{'twofactor_apikey'},
"\n");
&close_tempfile(PWFILE);
&unlock_file($miniserv{'userfile'});
@@ -652,7 +654,8 @@ else {
$user{'nochange'},":",
$user{'temppass'},":",
$user{'twofactor_provider'},":",
$user{'twofactor_id'},
$user{'twofactor_id'},":",
$user{'twofactor_apikey'},
"\n");
}
else {

View File

@@ -458,7 +458,7 @@ schema_download=Download schema file : <a href=$1>$1</a>
twofactor_err=Failed to setup two-factor authentication
twofactor_euser=Your Webmin user was not found!
twofactor_title=Two-Factor Authentication
twofactor_title=Your Two-Factor Authentication
twofactor_disable=Disable Two-Factor Authentication
twofactor_already=Your Webmin login already has two-factor authentication enabled with provider $1 and account ID $2.
twofactor_desc=This page allows you to enable two-factor authentication for your Webmin login using <a href='$2' target=_blank>$1</a>. Once active, an additional authentication token will be required when logging into Webmin.

View File

@@ -47,6 +47,7 @@ elsif ($in{'disable'}) {
# Turn off for this user
$user->{'twofactor_provider'} = undef;
$user->{'twofactor_id'} = undef;
$user->{'twofactor_apikey'} = undef;
&modify_user($user->{'name'}, $user);
&reload_miniserv();
&webmin_log("onefactor", "user", $user->{'name'});

View File

@@ -328,6 +328,7 @@ $user{'temppass'} = $in{'temp'};
if ($in{'cancel'}) {
$user->{'twofactor_provider'} = undef;
$user->{'twofactor_id'} = undef;
$user->{'twofactor_apikey'} = undef;
}
if ($in{'old'}) {

View File

@@ -12,13 +12,13 @@ require './acl-lib.pl';
$module_name eq 'acl' || die "Command must be run with full path";
# Check command-line args
@ARGV == 4 || die "Usage: $0 user provider id token";
($user, $provider, $id, $token) = @ARGV;
@ARGV == 5 || die "Usage: $0 user provider id token api-key";
($user, $provider, $id, $token, $apikey) = @ARGV;
# Call the provider validation function
&foreign_require("webmin");
$func = "webmin::validate_twofactor_".$provider;
$err = &$func($id, $token);
$err = &$func($id, $token, $apikey);
if ($err) {
$err =~ s/\r|\n/ /g;
print $err,"\n";

View File

@@ -4477,7 +4477,8 @@ if ($config{'userfile'}) {
$temppass{$user[0]} = $user[10];
if ($user[11] && $user[12]) {
$twofactor{$user[0]} = { 'provider' => $user[11],
'id' => $user[12] };
'id' => $user[12],
'apikey' => $user[13] };
}
}
close(USERS);
@@ -5928,7 +5929,8 @@ my $tf = $twofactor{$user};
$tf || return undef;
pipe(TOKENr, TOKENw);
my $pid = &execute_webmin_command($config{'twofactor_wrapper'},
[ $user, $tf->{'provider'}, $tf->{'id'}, $token ], TOKENw);
[ $user, $tf->{'provider'}, $tf->{'id'}, $token, $tf->{'apikey'} ],
TOKENw);
close(TOKENw);
waitpid($pid, 0);
my $ex = $?;

View File

@@ -4,29 +4,32 @@
require './webmin-lib.pl';
&ReadParse();
&error_setup($text{'twofactor_err'});
&get_miniserv_config(\%miniserv);
# Validate inputs
if ($in{'twofactor_provider'}) {
($got) = grep { $_->[0] eq $in{'twofactor_provider'} }
&list_twofactor_providers();
$got || &error($text{'twofactor_eprovider'});
$in{'twofactor_apikey'} =~ /^\S+$/ ||
&error($text{'twofactor_eapikey'});
($prov) = grep { $_->[0] eq $in{'twofactor_provider'} }
&list_twofactor_providers();
$prov || &error($text{'twofactor_eprovider'});
$vfunc = "validate_twofactor_apikey_".$in{'twofactor_provider'};
$err = defined(&$vfunc) && &$vfunc($in{'twofactor_apikey'},
$in{'twofactor_test'});
$err = defined(&$vfunc) && &$vfunc(\%in, \%miniserv);
&error($err) if ($err);
}
# Save settings
&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
$miniserv{'twofactor_provider'} = $in{'twofactor_provider'};
$miniserv{'twofactor_apikey'} = $in{'twofactor_apikey'};
$miniserv{'twofactor_test'} = $in{'twofactor_test'};
&put_miniserv_config(\%miniserv);
&unlock_file($ENV{'MINISERV_CONFIG'});
&show_restart_page();
$msg = $text{'restart_done'}."<p>\n";
if ($in{'twofactor_provider'}) {
$msg .= &text('twofactor_enrolllink',
"../acl/twofactor_form.cgi")."<p>\n";
if ($prov->[2]) {
$msg .= &text('twofactor_url', $prov->[1], $prov->[2])."<p>\n";
}
}
&show_restart_page($text{'twofactor_title'}, $msg);
&webmin_log("twofactor", undef, undef, \%in);

View File

@@ -2,28 +2,49 @@
# Show two-factor authentication options
require './webmin-lib.pl';
ui_print_header(undef, $text{'twofactor_title'}, "");
ui_print_header(undef, $text{'twofactor_title'}, "", "twofactor");
get_miniserv_config(\%miniserv);
print "$text{'twofactor_desc'}<p>\n";
print <<EOF;
<style>
.opener_shown {display:inline}
.opener_hidden {display:none}
</style>
EOF
@provs = &list_twofactor_providers();
print "<script>\n";
print "function show_prov(name) {\n";
foreach $p (@provs) {
print "d = document.getElementById(\"$p->[0]\");\n";
print "d.className = name == \"$p->[0]\" ? \"opener_shown\" : \"opener_hidden\";\n";
}
print "}\n";
print "</script>\n";
print ui_form_start("change_twofactor.cgi", "post");
print ui_table_start($text{'twofactor_header'}, undef, 2);
# Two-factor provider
my ($name, $value, $opts, $size, $multiple, $missing, $dis, $js) = @_;
print ui_table_row($text{'twofactor_provider'},
ui_select("twofactor_provider", $miniserv{'twofactor_provider'},
[ [ "", "&lt;".$text{'twofactor_none'}."&gt;" ],
map { [ $_->[0], $_->[1]." - ".$_->[2] ] }
&list_twofactor_providers() ]));
map { [ $_->[0], $_->[1] ] } @provs ],
1, 0, 0, 0, "onChange='show_prov(value)'"));
# API key
print ui_table_row($text{'twofactor_apikey'},
ui_textbox("twofactor_apikey", $miniserv{'twofactor_apikey'}, 40));
# Test mode?
print ui_table_row($text{'twofactor_test'},
ui_yesno_radio("twofactor_test", $miniserv{'twofactor_test'}));
foreach $p (@provs) {
$dis = $p->[0] eq $miniserv{'twofactor_provider'} ? "opener_shown"
: "opener_hidden";
print "<div id=$p->[0] name=$p->[0] class='$dis'>\n";
$sfunc = "show_twofactor_apikey_".$p->[0];
if (defined(&$sfunc)) {
print &$sfunc(\%miniserv);
}
print "</div>\n";
}
print ui_table_end();
print ui_form_end([ [ "save", $text{'save'} ] ]);

View File

@@ -1025,13 +1025,14 @@ twofactor_title=Two-Factor Authentication
twofactor_header=Two-factor authentication sessions
twofactor_provider=Authentication provider
twofactor_none=None
twofactor_apikey=Provider API key
twofactor_apikey=Authy API key
twofactor_test=Use provider's test mode?
twofactor_desc=Two-factor authentication allows Webmin users to enable use of an additional authentication device when logging in, such as a one-time passcode generator. Users must individually enroll with the selected authentication provider after it is enabled on this page.
twofactor_err=Failed to save two-factor authentication
twofactor_eprovider=Invalid provider!
twofactor_eapikey=Missing or invalid-looking API key
twofactor_authy=Authy
twofactor_topt=Google Authenticator
twofactor_email=Your email address
twofactor_country=Cellphone country code
twofactor_phone=Cellphone phone number
@@ -1044,5 +1045,7 @@ twofactor_eauthyenroll=Enrollment failed : $1
twofactor_eauthyid=Authy user ID must be a number
twofactor_eauthytoken=Authy token must be a number
twofactor_eauthyotp=Authy token is invalid
twofactor_enrolllink=You can now enroll for two-factor authentication in the <a href='$1'>Webmin Users</a> module.
twofactor_url=To learn more about $1, see it's website at <a href='$2' target=_blank>$2</a>.
__norefs=1

View File

@@ -2168,15 +2168,32 @@ if ($tryerror) {
# containing an ID, name and URL for more info
sub list_twofactor_providers
{
return ( [ 'authy', $text{'twofactor_authy'},
return ( [ 'topt', $text{'twofactor_topt'},
'http://en.wikipedia.org/wiki/Google_Authenticator' ],
[ 'authy', $text{'twofactor_authy'},
'http://www.authy.com/' ] );
}
# validate_twofactor_apikey_authy(apikey, test-mode)
# Check that an API key is valid
# show_twofactor_apikey_authy(&miniserv)
# Returns HTML for the form for authy-specific provider inputs
sub show_twofactor_apikey_authy
{
my ($miniserv) = @_;
my $rv;
$rv .= ui_table_row($text{'twofactor_apikey'},
ui_textbox("authy_apikey", $miniserv->{'twofactor_apikey'}, 40));
return $rv;
}
# validate_twofactor_apikey_authy(&in, &miniserv)
# Validates inputs from show_twofactor_apikey_authy, and stores them. Returns undef
# if OK, or an error message on failure
sub validate_twofactor_apikey_authy
{
my ($key, $test) = @_;
my ($in, $miniserv) = @_;
my $key = $in->{'authy_apikey'};
my $test = $miniserv->{'twofactor_test'};
$key =~ /^\S+$/ || return $text{'twofactor_eapikey'};
my $host = $test ? "sandbox-api.authy.com" : "api.authy.com";
my $port = $test ? 80 : 443;
my $page = "/protected/xml/app/details?api_key=".&urlize($key);
@@ -2190,6 +2207,7 @@ if ($err =~ /401/) {
elsif ($err) {
return &text('twofactor_eauthy', $err);
}
$miniserv->{'twofactor_apikey'} = $key;
return undef;
}
@@ -2246,6 +2264,7 @@ my ($out, $err);
return $err if ($err);
if ($out =~ /<id[^>]*>([^<]+)<\/id>/i) {
$user->{'twofactor_id'} = $1;
$user->{'twofactor_apikey'} = $miniserv{'twofactor_apikey'};
return undef;
}
else {
@@ -2254,11 +2273,11 @@ else {
}
}
# validate_twofactor_authy(id, token)
# validate_twofactor_authy(id, token, apikey)
# Checks the validity of some token for a user ID
sub validate_twofactor_authy
{
my ($id, $token) = @_;
my ($id, $token, $apikey) = @_;
$id =~ /^\d+$/ || return $text{'twofactor_eauthyid'};
$token =~ /^\d+$/ || return $text{'twofactor_eauthytoken'};
my %miniserv;
@@ -2266,8 +2285,8 @@ my %miniserv;
my $host = $miniserv{'twofactor_test'} ? "sandbox-api.authy.com"
: "api.authy.com";
my $port = $miniserv{'twofactor_test'} ? 80 : 443;
my $page = "/protected/xml/verify/$token/$id?api_key=".
&urlize($miniserv{'twofactor_apikey'});
my $page = "/protected/xml/verify/$token/$id?api_key=".&urlize($apikey).
"&force=true";
my $ssl = $miniserv{'twofactor_test'} ? 0 : 1;
my ($out, $err);
&http_download($host, $port, $page, \$out, \$err, undef, $ssl, undef, undef,
@@ -2300,4 +2319,17 @@ else {
}
}
# validate_twofactor_apikey_topt()
# Checks that the needed Perl module for TOPT is installed
sub validate_twofactor_apikey_topt
{
# XXX
}
# show_twofactor_form_topt()
# XXX is this even needed?
sub show_twofactor_form_topt
{
}
1;