From d8907f11f5c255727b8a814746a0114e5c62e30a Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Sun, 4 May 2014 18:35:10 +0200 Subject: Support Google Apps authentication. --- build.PL | 2 ++ lib/Net/MulkyID/Setup.pm | 49 ++++++++++++++++++++++++++++++----------------- setup.pl | 1 + www/authenticate.pl | 48 ++++++++++++++++++++++++++++++++++++++++++++++ www/common.pl | 13 +++++++++++++ www/failed-login.html | 17 ++++++++++++++++ www/failed-login.js | 1 + www/logged_in_p.pl | 3 ++- www/login.pl | 42 ++++++++++++++++++++++++++++++---------- www/provision.js | 6 +++--- www/sign.pl | 5 +++-- www/successful-login.html | 17 ++++++++++++++++ www/successful-login.js | 1 + 13 files changed, 171 insertions(+), 34 deletions(-) create mode 100755 www/authenticate.pl create mode 100644 www/failed-login.html create mode 100644 www/failed-login.js create mode 100644 www/successful-login.html create mode 100644 www/successful-login.js diff --git a/build.PL b/build.PL index 342fae0..17d2536 100644 --- a/build.PL +++ b/build.PL @@ -24,6 +24,8 @@ my $build = Net::MulkyID::Builder->new "Mail::IMAPTalk" => 0, "Modern::Perl" => 0, "Time::HiRes" => 0, + "Syntax::Keyword::Junction" => 0, + "Net::Google::FederatedLogin" => 0, }, build_requires => { "LWP::Simple" => 0, diff --git a/lib/Net/MulkyID/Setup.pm b/lib/Net/MulkyID/Setup.pm index b79f96b..acfe018 100755 --- a/lib/Net/MulkyID/Setup.pm +++ b/lib/Net/MulkyID/Setup.pm @@ -1,5 +1,5 @@ #! /usr/bin/env perl -# Copyright 2012, Matthias Andreas Benkard . +# Copyright 2012, 2014, Matthias Andreas Benkard . package Net::MulkyID::Setup; @@ -30,13 +30,13 @@ sub prompt($$) { } } -sub makespec($) { - my ($key) = @_; +sub makespec($$) { + my ($key, $basepath) = @_; my ($n, $e, @stuff) = $key->get_key_parameters; return encode_json({"public-key" => {e => $e->to_decimal, n => $n->to_decimal, algorithm => "RS"}, - "authentication" => "/browserid/authenticate.html", - "provisioning" => "/browserid/provision.html"}); + "authentication" => "$basepath/authenticate.html", + "provisioning" => "$basepath/provision.html"}); }; sub setup() { @@ -49,16 +49,29 @@ sub setup() { do $conffile; } - my $configpath = $::MULKONF->{configpath} // "/etc/mulkyid"; - $configpath = prompt("Where shall I put configuration files?", $configpath); - my $pemfile = $::MULKONF->{pemfile} // "$configpath/rsa2048.pem"; - $pemfile = prompt("Where shall I put the private key?", $pemfile); + my $configpath = $::MULKONF->{configpath} // "/etc/mulkyid"; + my $pemfile = $::MULKONF->{pemfile} // "$configpath/rsa2048.pem"; + my $auth_type = $::MULKONF->{auth_type} // "imap"; my $aliases_file = $::MULKONF->{aliases_file} // "/etc/aliases"; - $aliases_file = prompt("Where is the aliases file? Type a single dot for none.", $aliases_file); - my $imap_server = $::MULKONF->{imap_server} // "localhost"; - $imap_server = prompt("What is the IMAP server's address?", $imap_server); - my $imap_port = $::MULKONF->{imap_port} // 143; - $imap_port = int(prompt("What is the IMAP server's port?", $imap_port)); + my $imap_server = $::MULKONF->{imap_server} // "localhost"; + my $imap_port = $::MULKONF->{imap_port} // 143; + my $basepath = $::MULKONF->{basepath} // "/browserid"; + $configpath = prompt("Where shall I put configuration files?", $configpath); + $pemfile = prompt("Where shall I put the private key?", $pemfile); + $auth_type = prompt("How will users authenticate? (imap, google)", $auth_type); + $basepath = int(prompt("What will be the web-facing base path for IdP files and scripts?", $basepath)); + for ($auth_type) { + when ("imap") { + $aliases_file = prompt("Where is the aliases file? Type a single dot for none.", $aliases_file); + $imap_server = prompt("What is the IMAP server's address?", $imap_server); + $imap_port = int(prompt("What is the IMAP server's port?", $imap_port)); + } + when ("google") { + } + default { + die "Invalid authentication type"; + } + } say "OK."; @@ -81,8 +94,7 @@ sub setup() { } else { say "Generating private key..."; $key = Crypt::OpenSSL::RSA->generate_key(2048); - make_path($configpath) - or die "Could not create directory: $configpath"; + make_path($configpath); write_file($pemfile, $key->get_private_key_string()) or die "Could not write private key to $pemfile: $!"; say "Private key saved to: $pemfile"; @@ -90,7 +102,7 @@ sub setup() { } # Generate spec file. - write_file("browserid.json", makespec $key) + write_file("browserid.json", makespec($key, $basepath)) or die "Could not write spec to browserid.json: $!"; say "Persona spec file saved to: browserid.json"; @@ -100,7 +112,8 @@ sub setup() { pemfile => $pemfile, aliases_file => $aliases_file, imap_server => $imap_server, - imap_port => $imap_port + imap_port => $imap_port, + basepath => $basepath, }; write_file($conffile, <. + +use common::sense; +#use Modern::Perl 2011; +use Modern::Perl; + +use JSON; + +use CGI; +use CGI::Fast; +use CGI::Session; + +use Net::Google::FederatedLogin; + +do "common.pl"; + +while (my $cgi = new CGI::Fast) { + load_config(); + + my $claimed_email = $cgi->param('email'); + + my $g = Net::Google::FederatedLogin->new( + claimed_id => $claimed_email, + return_to => reluri($cgi, 'login.pl'), + extensions => [{ns => 'ax', + uri => 'http://openid.net/srv/ax/1.0', + attributes => {mode => 'fetch_request', + required => 'email', + type => {email => 'http://axschema.org/contact/email'}}}] + ); + + my $cookie = $cgi->cookie('mulkid_session'); + my $session = new CGI::Session("driver:File", $cookie, {Directory=>"/tmp"}); + $session->param('claimed_email', $claimed_email); + if ($cookie) { + print $cgi->redirect(-url => $g->get_auth_url()); + } else { + my $cookie = $cgi->cookie(-name => 'mulkid_session', + -value => $session->id, + -expires => '+1d', + #-secure => 1, + -httponly => 1, + #-domain => '.'.$::MULKONF->{realm} + ); + print $cgi->redirect(-cookie => $cookie, -url => $g->get_auth_url()); + } +} diff --git a/www/common.pl b/www/common.pl index aa86e47..3f3a5b0 100644 --- a/www/common.pl +++ b/www/common.pl @@ -6,6 +6,7 @@ use common::sense; use Modern::Perl; use Mail::ExpandAliases; +use URI; sub load_config() { $::MULKONF = { }; @@ -13,6 +14,8 @@ sub load_config() { } sub email_users($) { + return @_ + if $::MULKONF->{auth_type} eq 'google'; my ($email) = @_; my $alias; if ($email =~ /^(.*?)@/) { $alias = $1; } @@ -25,3 +28,13 @@ sub email_users($) { return ($alias); } } + +sub reluri($$) { + my ($cgi, $x) = @_; + my $uri = URI->new($cgi->url(-full=>1)); + my @path = $uri->path_segments; + pop @path; + push @path, $x; + $uri->path_segments(@path); + return "$uri"; +} diff --git a/www/failed-login.html b/www/failed-login.html new file mode 100644 index 0000000..9838d21 --- /dev/null +++ b/www/failed-login.html @@ -0,0 +1,17 @@ + + + + MulkyID Persona Login + + + + + + + + +

MulkyID Persona Login

+ +

Login failure!

+ + diff --git a/www/failed-login.js b/www/failed-login.js new file mode 100644 index 0000000..3618002 --- /dev/null +++ b/www/failed-login.js @@ -0,0 +1 @@ +navigator.id.raiseAuthenticationFailure(); diff --git a/www/logged_in_p.pl b/www/logged_in_p.pl index 2e648fc..73c9d1c 100755 --- a/www/logged_in_p.pl +++ b/www/logged_in_p.pl @@ -4,6 +4,7 @@ use common::sense; use Modern::Perl; +use syntax 'junction'; use JSON; @@ -35,7 +36,7 @@ while (my $cgi = new CGI::Fast) { my $email = $cgi->param('email') or die "No email address supplied"; my $session_user = $session->param('user'); - if ($session_user ~~ [email_users($email)]) { + if (any(email_users($email)) eq $session_user) { say encode_json({logged_in_p => 1}); } else { say encode_json({logged_in_p => 0}); diff --git a/www/login.pl b/www/login.pl index 9e0467e..a2f06c4 100755 --- a/www/login.pl +++ b/www/login.pl @@ -12,11 +12,12 @@ use CGI::Fast; use CGI::Session; use Mail::IMAPTalk ; +use Net::Google::FederatedLogin; #use IO::Socket::SSL; do "common.pl"; -sub check_password($$) { +sub check_imap_password($$) { my ($user, $password) = @_; #my $socket = IO::Socket::SSL->new('imap.googlemail.com:imaps'); @@ -38,7 +39,7 @@ sub check_password($$) { while (my $cgi = new CGI::Fast) { - load_config(); + load_config; my $cookie = $cgi->cookie('mulkid_session'); my $session; @@ -58,17 +59,38 @@ while (my $cgi = new CGI::Fast) { -cookie => $cookie); } - my $email = $cgi->param('email') or die "No email address provided"; - my $password = $cgi->param('password') or die "Empty password"; + my $email = $cgi->param('email') or die "No email address provided"; - for my $user (email_users($email)) { - #say STDERR "Trying user: $user"; - if (check_password($user, $password)) { - $session->param('user', $user); - say encode_json({user => $user}); + for ($::MULKONF->{auth_type}) { + when ('imap') { + my $password = $cgi->param('password') or die "Empty password"; + for my $user (email_users($email)) { + #say STDERR "Trying user: $user"; + if (check_imap_password($user, $password)) { + $session->param('user', $user); + #say encode_json({user => $user}); + print $cgi->redirect(-url => reluri($cgi, 'successful-login.html')); + exit 0; + } + } + } + when ('google') { + my $g = Net::Google::FederatedLogin->new( + cgi => $cgi, + return_to => $cgi->url() + ); + $g->verify_auth or die "Could not verify the OpenID assertion!"; + my $ext = $g->get_extension('http://openid.net/srv/ax/1.0'); + my $verified_email = $ext->get_parameter('value.email'); + $session->param('user', $verified_email); + #say encode_json({user => $user}); + print $cgi->redirect(-url => reluri($cgi, 'successful-login.html')); exit 0; } + default { + die "Invalid auth_type. Check MulkyID configuration!"; + } } - die "Could not authenticate with mail server."; + die "Could not authenticate."; } diff --git a/www/provision.js b/www/provision.js index 5441bce..4113be0 100644 --- a/www/provision.js +++ b/www/provision.js @@ -10,7 +10,7 @@ jQuery(function($) { $.ajax({ type: 'POST', crossDomain: true, - url: '/browserid/logged_in_p.pl', + url: 'logged_in_p.pl', dataType: 'json', data: { email: email }, success: function(data, status, xhr) { @@ -19,7 +19,7 @@ jQuery(function($) { console.log('* Not logged in.'); return navigator.id.raiseProvisioningFailure('user is not authenticated as target user'); } else { - thunk(); + return thunk(); } }, error: function(data, status, xhr) { @@ -40,7 +40,7 @@ jQuery(function($) { dataType: 'json', data: { email: email, pubkey: JSON.stringify(pubkey), duration: cert_duration }, success: function(data, status, xhr) { - console.log('Success!'); + console.log('Success!'); console.log(data); navigator.id.registerCertificate(data.signature); }, diff --git a/www/sign.pl b/www/sign.pl index a0fd11f..b16ff86 100755 --- a/www/sign.pl +++ b/www/sign.pl @@ -4,6 +4,7 @@ use common::sense; use Modern::Perl; +use syntax 'junction'; use JSON; @@ -85,8 +86,8 @@ while (my $cgi = new CGI::Fast) { my $domain; if ($email =~ /^(.*?)@(.*)/) { $domain = $2; } - die "User is not authorized to use this email address" - unless ($session_user ~~ [email_users($email)]); + die "User $session_user is not authorized to use this email address ($email)" + unless any(email_users($email)) eq $session_user; my $sig = sign $key, decode_json($user_pubkey), $email, $duration, $domain; say encode_json({signature => $sig}); diff --git a/www/successful-login.html b/www/successful-login.html new file mode 100644 index 0000000..4f0886c --- /dev/null +++ b/www/successful-login.html @@ -0,0 +1,17 @@ + + + + MulkyID Persona Login + + + + + + + + +

MulkyID Persona Login

+ +

Success!

+ + diff --git a/www/successful-login.js b/www/successful-login.js new file mode 100644 index 0000000..bad7e39 --- /dev/null +++ b/www/successful-login.js @@ -0,0 +1 @@ +navigator.id.completeAuthentication(); -- cgit v1.2.3