From 785e4e1aec37cf16076076b6c544c14dc67f0c75 Mon Sep 17 00:00:00 2001 From: Nicholas van Oudtshoorn Date: Tue, 14 Jul 2015 13:10:33 +0800 Subject: [PATCH] [PATCH] Bug 10988: Allow login via Google OAuth2 Adds googleoauth2 login to koha. Adds applicable system preferences Modifies templates to support oauth2 login failures changes the login links to use googleoauth2 when oauth2 is enabled. Updated patch to address the concerns in Comment 36. Test Plan: 0) Back up your database 1) Apply this patch 2) In your mysql client use your Koha database and execute: > DELETE FROM systempreferences; > SOURCE ~/kohaclone/installer/data/mysql/sysprefs.sql; -- Should be no errors. > SELECT * FROM systempreferences LIKE 'GoogleOAuth%'; -- Should see 4 entries. > QUIT; 3) Restore your database 4) Run ./installer/data/mysql/updatedatabase.pl; 5) In your mysql client use your Koha database and execute: > SELECT * FROM systempreferences LIKE 'GoogleOAuth%'; -- Should see the same 4 entries. 6) Log into the staff client 7) Home -> Koha administration -> Global system preferences 8) -> OPAC -- make sure your OPACBaseURL is set (e.g. https://opac.koha.ca) 9) -> Administration -- There should be a 'Google OAuth2' section with the ability to set those 4 system preferences. 10) In a new tab, go to https://console.developers.google.com/project 11) Click 'Create Project' 12) Type in a project name that won't freak users out, like your library name (e.g. South Pole Library). 13) Click the 'Create' button. 14) Click the 'APIs & auth' in the left frame. 15) Click 'Credentials' 16) Click 'Create new Client ID' 17) Select 'Web application' and click 'Configure consent screen'. 18) Select the Email Address. 19) Put it a meaningful string into the Product Name (e.g. South Pole Library Authentication) 20) Fill in the other fields as desired (or not) 21) Click 'Save' 22) Change the 'AUTHORIZED JAVASCRIPT ORIGINS' to your OPACBaseURL. (http://library.yourDNS.org) 23) Change the 'AUTHORIZED REDIRECT URIS' to point to the new googleoauth2 script (http://library.yourDNS.org/cgi-bin/koha/svc/auth/googleoauth2) 24) Click 'Create Client ID' 25) Copy and paste the 'CLIENT ID' into the GoogleOAuth2ClientID system preference. 26) Copy and paste the 'CLIENT SECRET' into the GoogleOAuth2ClientSecret system preference. 27) Change the GoogleOAuth2 preference to 'Use'. 28) Click 'Save all Administration preferences' 29) In the OPAC, click 'Log in to your account'. -- You should get a confirmation request, if you are already logged in, OR a login screen if you are not. -- You need to have the primary email address set to one authenticated by Google in order to log in. 30) Run koha qa test tools --- C4/Auth.pm | 7 + installer/data/mysql/atomicupdate/googleoauth2.sql | 5 + installer/data/mysql/sysprefs.sql | 4 + .../prog/en/modules/admin/preferences/admin.pref | 18 +++ .../opac-tmpl/bootstrap/en/includes/masthead.inc | 4 + .../opac-tmpl/bootstrap/en/modules/opac-auth.tt | 9 ++ opac/svc/auth/googleoauth2 | 169 +++++++++++++++++++++ 7 files changed, 216 insertions(+) create mode 100644 installer/data/mysql/atomicupdate/googleoauth2.sql create mode 100755 opac/svc/auth/googleoauth2 diff --git a/C4/Auth.pm b/C4/Auth.pm index 268ef35..4b6cd25 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -1247,6 +1247,13 @@ sub checkauth { ); } + if (C4::Context->preference('GoogleOAuth2')) { + if ($query->param("OAuth2Failed")) { + my $reason = $query->param('OAuth2Failed'); + $template->param(invalidOAuth2Login => $reason); + } + } + my $self_url = $query->url( -absolute => 1 ); $template->param( url => $self_url, diff --git a/installer/data/mysql/atomicupdate/googleoauth2.sql b/installer/data/mysql/atomicupdate/googleoauth2.sql new file mode 100644 index 0000000..9096d24 --- /dev/null +++ b/installer/data/mysql/atomicupdate/googleoauth2.sql @@ -0,0 +1,5 @@ +INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES + ('GoogleOAuth2', '0', NULL, 'if ON, allows the use of Google OAuth2 for login', 'YesNo'), + ('GoogleOAuth2ClientID', '', NULL, 'Client ID for the web app registered with google', 'Free'), + ('GoogleOAuth2ClientSecret', '', NULL, 'Client Secret for the web app registered with google', 'Free'), + ('GoogleOAuth2Domain', '', NULL, 'Restrict OAuth2 to this domain (or subdomains of this domain). Leave blank for all google domains', 'Free'); diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index a343571..cfeba69 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -138,6 +138,10 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('gist','0','','Default Goods and Services tax rate NOT in %, but in numeric form (0.12 for 12%), set to 0 to disable GST','Integer'), ('GoogleIndicTransliteration','0','','GoogleIndicTransliteration on the OPAC.','YesNo'), ('GoogleJackets','0',NULL,'if ON, displays jacket covers from Google Books API','YesNo'), +('GoogleOAuth2', '0', NULL, 'if ON, allows the use of Google OAuth2 for login', 'YesNo'), +('GoogleOAuth2ClientID', '', NULL, 'Client ID for the web app registered with google', 'Free'), +('GoogleOAuth2ClientSecret', '', NULL, 'Client Secret for the web app registered with google', 'Free'), +('GoogleOAuth2Domain', '', NULL, 'Restrict OAuth2 to this domain (or subdomains of this domain). Leave blank for all google domains', 'Free'), ('hidelostitems','0','','If ON, disables display of\"lost\" items in OPAC.','YesNo'), ('HidePatronName','0','','If this is switched on, patron\'s cardnumber will be shown instead of their name on the holds and catalog screens','YesNo'), ('hide_marc','0',NULL,'If ON, disables display of MARC fields, subfield codes & indicators (still shows data)','YesNo'), diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref index 4ec683f..8ed2841 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref @@ -114,6 +114,24 @@ Administration: Common Name: the Common Name emailAddress: the emailAddress - field for SSL client certificate authentication + Google OAuth2: + - + - pref: GoogleOAuth2 + choices: + yes: Use + no: "Don't Use" + - Google OAuth2 login. + - You will need to select OAuth2 when creating an app in the google cloud console, and set the web origin to your_opac_url and the redirect url to your_opac_url/cgi-bin/koha/svc/auth/googleoauth2 . + - + - Google OAuth2 Client ID + - pref: GoogleOAuth2ClientID + - + - Google OAuth2 Client Secret + - pref: GoogleOAuth2ClientSecret + - + - Google OAuth2 Restrict to domain (or subdomain of this domain) + - pref: GoogleOAuth2Domain + - . Leave blank for all google domains Mozilla Persona: - - pref: Persona diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc index 1ccd2be..37fc757 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc @@ -59,6 +59,8 @@
  • No private lists
  • New list
  • [% END %] + [% ELSIF ( Koha.Preference('GoogleOAuth2') == 1 ) %] +
  • Log in to create your own lists
  • [% ELSE %]
  • Log in to create your own lists
  • [% END # / IF loggedinusername %] @@ -113,6 +115,8 @@ [% IF Koha.Preference('casAuthentication') %] [%# CAS authentication is too complicated for modal window %]
  • Log in to your account
  • + [% ELSIF ( Koha.Preference('GoogleOAuth2') == 1 ) %] +
  • Log in to your account
  • [% ELSE %]
  • Log in to your account
  • [% END %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt index c474791..28e206d 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt @@ -138,6 +138,15 @@ [% END # / IF casAuthentication %] + [% IF ( invalidOAuth2Login ) %] +

    Automatic Login

    +

    Sorry, your automatic login failed. [% invalidOAuth2Login %]

    +

    Please note that automatic login will only work if you are using the emal address registered with this library.

    +

    If you want to, you can try to login using a different account +

    Local login

    +

    If you can't login automatically, you can still login manually:

    + [% END %] +
    diff --git a/opac/svc/auth/googleoauth2 b/opac/svc/auth/googleoauth2 new file mode 100755 index 0000000..ee8826e --- /dev/null +++ b/opac/svc/auth/googleoauth2 @@ -0,0 +1,169 @@ +#!/usr/bin/perl +# Copyright vanoudt@gmail.com 2014 +# Based on persona code from chris@bigballofwax.co.nz 2013 +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . +# +# +# Basic OAuth2 authentication for google goes like this First: +# get your clientid, clientsecret from google. At this stage, tell +# google that your redirect url is /cgi-bin/koha/svc/oauthlogin +# +# The first thing that happens when this script is called is +# that one gets redirected to an authentication url from google +# +# If successful, that then redirects back to this script, setting +# a CODE parameter which we use to look up a json authentication +# token. This token includes an encrypted json id_token, which we +# round-trip back to google to decrypt. Finally, we can extract +# the email address from this. +# +# There is some room for improvement here. In particular, Google +# recommends verifying and decrypting the id_token locally, which +# means caching some information and updating it daily. But that +# would make things a lot faster + +use Modern::Perl; +use CGI qw ( -utf8 escape ); +use C4::Auth; +use C4::Context; +use C4::Output; + +use LWP::UserAgent; +use HTTP::Request::Common qw{ POST }; +use JSON; + +my $scope = "openid email"; +my $host = C4::Context->preference('OPACBaseURL') // ''; +my $restricttodomain = C4::Context->preference('GoogleOAuth2Domain') // ''; + +# protocol is assumed in OPACBaseURL see bug 5010. +my $redirecturl = $host . '/cgi-bin/koha/svc/googleoauth2'; +my $issuer = 'accounts.google.com'; +my $clientid = C4::Context->preference('GoogleOAuth2ClientID'); +my $clientsecret = C4::Context->preference('GoogleOAuth2ClientSecret'); + +my $query = new CGI; + +sub loginfailed { + my $query = shift; + my $reason = shift; + $query->delete('code'); + $query->param( 'OAuth2Failed' => $reason ); + my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { + template_name => 'opac-user.tt', + query => $query, + type => 'opac', + authnotrequired => 0, + flagsrequired => { borrow => 1 }, + } + ); + $template->param( 'invalidOAuth2Login' => $reason ); + $template->param( 'loginprompt' => 1 ); + output_html_with_http_headers $query, $cookie, $template->output; +} + +if ( defined $query->param('error') ) { + loginfailed( $query, + 'An authentication error occurred. (Error:' + . $query->param('error') + . ')' ); +} +elsif ( defined $query->param('code') ) { + my $code = $query->param('code'); + my $ua = LWP::UserAgent->new(); + my $request = POST( + 'https://accounts.google.com/o/oauth2/token', + [ + code => $code, + client_id => $clientid, + client_secret => $clientsecret, + redirect_uri => $redirecturl, + grant_type => 'authorization_code', + $scope => $scope + ] + ); + my $response = $ua->request($request)->decoded_content; + my $json = decode_json($response); + if ( exists( $json->{'id_token'} ) ) { + $request = POST( 'https://www.googleapis.com/oauth2/v1/tokeninfo', + [ id_token => $json->{'id_token'} ] ); + $response = $ua->request($request)->decoded_content; + $json = decode_json($response); + +# Confirm (as google suggests) that the issuer and audience are what we expect them to be + if ( ( $json->{'issuer'} eq $issuer ) + && ( $json->{'audience'} eq $clientid ) + && exists( $json->{'email'} ) ) + { + my $email = $json->{'email'}; + my ( $userid, $cookie, $sessionID ) = + checkauth( $query, 1, { borrow => 1 }, 'opac', $email ); + if ($userid) { # A valid user has logged in + if ( ( $restricttodomain ne '' ) + && ( index( $email, $restricttodomain ) == -1 ) ) + { + loginfailed( $query, +'The email you have used is not valid for this library. Email addresses should conclude with ' + . $restricttodomain + . ' .' ); + } + else { + print $query->redirect( + -uri => '/cgi-bin/koha/opac-user.pl', + -cookie => $cookie + ); + } + } + else { + loginfailed( $query, +'The email address you are trying to use is not associated with a borrower in this library.' + ); + } + } + else { # something went wrong with getting appropriate credentials + loginfailed( $query, + 'Failed to get proper credentials from google.' ); + } + } + else { # Failed to get ID Token + loginfailed( $query, +'An authentication error occurred. (Error: No ID Token was supplied)' + ); + } + +} +else { + my $prompt = ''; + $prompt = $query->param('reauthenticate') + unless not( defined $query->param('reauthenticate') ); + + my $authorisationurl = + 'https://accounts.google.com/o/oauth2/auth?' + . 'response_type=code&' + . 'redirect_uri=' + . escape($redirecturl) . '&' + . 'client_id=' + . escape($clientid) . '&' + . 'scope=' + . escape($scope) . '&'; + if ( $query->param('reauthenticate') ) { + $authorisationurl .= + 'prompt=' . escape( $query->param('reauthenticate') ); + } + print $query->redirect($authorisationurl); +} -- 2.1.0