1 package Koha::Auth::TwoFactorAuth;
3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use MIME::Base64 qw( encode_base64 );
25 use Koha::Exceptions::Patron;
27 use base qw( Auth::GoogleAuth );
31 Koha::Auth::TwoFactorAuth- Koha class deal with Two factor authentication
35 use Koha::Auth::TwoFactorAuth;
37 my $secret = Koha::AuthUtils::generate_salt( 'weak', 16 );
38 my $auth = Koha::Auth::TwoFactorAuth->new({ patron => $patron, secret => $secret });
39 my $image_src = $auth->qr_code;
40 my $ok = $auth->verify( $pin_code, 1 );
42 It's based on Auth::GoogleAuth
48 $obj = Koha::Auth::TwoFactorAuth->new({ patron => $p, secret => $s });
51 Secret is optional, defaults to patron's secret.
52 Passing secret32 overrules secret! Secret32 should be base32.
57 my ($class, $params) = @_;
58 my $patron = $params->{patron};
59 my $secret32 = $params->{secret32};
60 my $secret = $params->{secret};
62 # FIXME Raise an exception if the syspref is disabled
64 Koha::Exceptions::MissingParameter->throw("Mandatory patron parameter missing")
65 unless $patron && ref($patron) eq 'Koha::Patron';
67 my $type = 'secret32';
69 Koha::Exceptions::BadParameter->throw("Secret32 should be base32")
70 if $secret32 =~ /[^a-z2-7]/;
73 } elsif( $patron->secret ) {
74 $secret32 = $patron->decoded_secret; # saved already in base32
76 Koha::Exceptions::MissingParameter->throw("No secret passed or patron has no secret");
79 my $issuer = Encode::encode_utf8($patron->library->branchname);
80 my $key_id = sprintf "%s_%s",
81 $issuer, ( $patron->email || $patron->userid );
83 return $class->SUPER::new({
84 $type => $secret32 || $secret,
92 my $image_src = $auth->qr_code;
94 Replacement for (unsafer) Auth::GoogleAuth::qr_code.
95 Returns the data URL to fill the src attribute of the
96 image tag on the registration form.
103 my $otpauth = $self->SUPER::qr_code( undef, undef, undef, 1);
104 # no need to pass secret, key and issuer again
105 my $qrcode = GD::Barcode->new( 'QRcode', $otpauth, { Ecc => 'M', ModuleSize => 4 } );
106 my $data = $qrcode->plot->png;
107 return "data:image/png;base64,". encode_base64( $data, q{} ); # does not contain newlines
112 my $verified = $auth->verify($otp_token);
114 Replacement for Auth::GoogleAuth::verify.
115 This uses a system wide default for range.
120 my ( $self, $code, $range, $secret32, $timestamp, $interval ) = @_;
121 if ( !defined $range ) {
122 my $mfa_range = C4::Context->config('mfa_range') ? int( C4::Context->config('mfa_range') ) : 1;
127 return $self->SUPER::verify( $code, $range, $secret32, $timestamp, $interval );