View | Details | Raw Unified | Return to bug 30588
Collapse All | Expand All

(-)a/C4/Auth.pm (-3 / +14 lines)
Lines 1314-1322 sub checkauth { Link Here
1314
        # Auth is completed unless an additional auth is needed
1314
        # Auth is completed unless an additional auth is needed
1315
        if ( $require_2FA ) {
1315
        if ( $require_2FA ) {
1316
            my $patron = Koha::Patrons->find({userid => $userid});
1316
            my $patron = Koha::Patrons->find({userid => $userid});
1317
            if ( C4::Context->preference('TwoFactorAuthentication') eq "enforced"
1317
            if ( C4::Context->preference('TwoFactorAuthentication') eq "enforced" && $patron->auth_method eq 'password' ) {
1318
                || $patron->auth_method eq 'two-factor' )
1318
                $auth_state = 'setup-additional-auth-needed';
1319
            {
1319
                $session->param('waiting-for-2FA-setup', 1);
1320
                %info = ();# We remove the warnings/errors we may have set incorrectly before
1321
            } elsif ( $patron->auth_method eq 'two-factor' ) {
1320
                # Ask for the OTP token
1322
                # Ask for the OTP token
1321
                $auth_state = 'additional-auth-needed';
1323
                $auth_state = 'additional-auth-needed';
1322
                $session->param('waiting-for-2FA', 1);
1324
                $session->param('waiting-for-2FA', 1);
Lines 1428-1433 sub checkauth { Link Here
1428
        );
1430
        );
1429
    }
1431
    }
1430
1432
1433
    if ( $auth_state eq 'setup-additional-auth-needed' ) {
1434
        $template->param(
1435
            TwoFA_setup => 1,
1436
        );
1437
    }
1438
1431
    if ( $type eq 'opac' ) {
1439
    if ( $type eq 'opac' ) {
1432
        require Koha::Virtualshelves;
1440
        require Koha::Virtualshelves;
1433
        my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
1441
        my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
Lines 1821-1826 sub check_cookie_auth { Link Here
1821
                return ( "additional-auth-needed", $session )
1829
                return ( "additional-auth-needed", $session )
1822
                    if $session->param('waiting-for-2FA');
1830
                    if $session->param('waiting-for-2FA');
1823
1831
1832
                return ( "setup-additional-auth-needed", $session )
1833
                    if $session->param('waiting-for-2FA-setup');
1834
1824
                return ( "ok", $session );
1835
                return ( "ok", $session );
1825
            } else {
1836
            } else {
1826
                $session->delete();
1837
                $session->delete();
(-)a/Koha/Auth/TwoFactorAuth.pm (+2 lines)
Lines 58-63 sub new { Link Here
58
    my $secret32 = $params->{secret32};
58
    my $secret32 = $params->{secret32};
59
    my $secret = $params->{secret};
59
    my $secret = $params->{secret};
60
60
61
    # FIXME Raise an exception if the syspref is disabled
62
61
    Koha::Exceptions::MissingParameter->throw("Mandatory patron parameter missing")
63
    Koha::Exceptions::MissingParameter->throw("Mandatory patron parameter missing")
62
        unless $patron && ref($patron) eq 'Koha::Patron';
64
        unless $patron && ref($patron) eq 'Koha::Patron';
63
65
(-)a/Koha/REST/V1/Auth.pm (+22 lines)
Lines 236-241 sub authenticate_api_request { Link Here
236
                Koha::Exceptions::Authentication::Required->throw(
236
                Koha::Exceptions::Authentication::Required->throw(
237
                    error => 'Authentication failure.' );
237
                    error => 'Authentication failure.' );
238
            }
238
            }
239
        }
240
        elsif (  $c->req->url->to_abs->path eq '/api/v1/auth/two-factor/registration'
241
              || $c->req->url->to_abs->path eq '/api/v1/auth/two-factor/registration/verification' ) {
242
243
            if ( $status eq 'setup-additional-auth-needed' ) {
244
                $user        = Koha::Patrons->find( $session->param('number') );
245
                $cookie_auth = 1;
246
            }
247
            elsif ( $status eq 'ok' ) {
248
                $user = Koha::Patrons->find( $session->param('number') );
249
                if ( $user->auth_method ne 'password' ) {
250
                    # If the user already enabled 2FA they don't need to register again
251
                    Koha::Exceptions::Authentication->throw(
252
                        error => 'Cannot request this route.' );
253
                }
254
                $cookie_auth = 1;
255
            }
256
            else {
257
                Koha::Exceptions::Authentication::Required->throw(
258
                    error => 'Authentication failure.' );
259
            }
260
239
        } else {
261
        } else {
240
            if ($status eq "ok") {
262
            if ($status eq "ok") {
241
                $user = Koha::Patrons->find( $session->param('number') );
263
                $user = Koha::Patrons->find( $session->param('number') );
(-)a/Koha/REST/V1/TwoFactorAuth.pm (+103 lines)
Lines 78-81 sub send_otp_token { Link Here
78
78
79
}
79
}
80
80
81
=head3 registration
82
83
Ask for a registration secret. It will return a QR code image and a secret32.
84
85
The secret must be sent back to the server with the pin code for the verification step.
86
87
=cut
88
89
sub registration {
90
91
    my $c = shift->openapi->valid_input or return;
92
93
    my $patron = Koha::Patrons->find( $c->stash('koha.user')->borrowernumber );
94
95
    return try {
96
        my $secret = Koha::AuthUtils::generate_salt( 'weak', 16 );
97
        my $auth   = Koha::Auth::TwoFactorAuth->new(
98
            { patron => $patron, secret => $secret } );
99
100
        my $response = {
101
            issuer   => $auth->issuer,
102
            key_id   => $auth->key_id,
103
            qr_code  => $auth->qr_code,
104
            secret32 => $auth->secret32,
105
106
            # IMPORTANT: get secret32 after qr_code call !
107
        };
108
        $auth->clear;
109
110
        return $c->render(status => 201, openapi => $response);
111
    }
112
    catch {
113
        $c->unhandled_exception($_);
114
    };
115
116
}
117
118
=head3 verification
119
120
Verify the registration, get the pin code and the secret retrieved from the registration.
121
122
The 2FA_ENABLE notice will be generated if the pin code is correct, and the patron will have their two-factor authentication setup completed.
123
124
=cut
125
126
sub verification {
127
128
    my $c = shift->openapi->valid_input or return;
129
130
    my $patron = Koha::Patrons->find( $c->stash('koha.user')->borrowernumber );
131
132
    return try {
133
134
        my $pin_code = $c->validation->param('pin_code');
135
        my $secret32 = $c->validation->param('secret32');
136
137
        my $auth     = Koha::Auth::TwoFactorAuth->new(
138
            { patron => $patron, secret32 => $secret32 } );
139
140
        my $verified = $auth->verify(
141
            $pin_code,
142
            1,        # range
143
            $secret32,
144
            undef,    # timestamp (defaults to now)
145
            30,       # interval (default 30)
146
        );
147
148
        unless ($verified) {
149
            return $c->render(
150
                status  => 400,
151
                openapi => { error => "Invalid pin" }
152
            );
153
        }
154
155
        # FIXME Generate a (new?) secret
156
        $patron->encode_secret($secret32);
157
        $patron->auth_method('two-factor')->store;
158
        if ( $patron->notice_email_address ) {
159
            $patron->queue_notice(
160
                {
161
                    letter_params => {
162
                        module      => 'members',
163
                        letter_code => '2FA_ENABLE',
164
                        branchcode  => $patron->branchcode,
165
                        lang        => $patron->lang,
166
                        tables      => {
167
                            branches  => $patron->branchcode,
168
                            borrowers => $patron->id
169
                        },
170
                    },
171
                    message_transports => ['email'],
172
                }
173
            );
174
        }
175
176
        return $c->render(status => 204, openapi => {});
177
    }
178
    catch {
179
        $c->unhandled_exception($_);
180
    };
181
182
}
183
81
1;
184
1;
(-)a/api/v1/swagger/paths/auth.yaml (+89 lines)
Lines 39-41 Link Here
39
    x-koha-authorization:
39
    x-koha-authorization:
40
      permissions:
40
      permissions:
41
        catalogue: "1"
41
        catalogue: "1"
42
43
/auth/two-factor/registration:
44
  post:
45
    x-mojo-to: TwoFactorAuth#registration
46
    operationId: Two factor register
47
    tags:
48
      - 2fa
49
    summary: Generate a secret
50
    produces:
51
      - application/json
52
    responses:
53
      "201":
54
        description: OK
55
        schema:
56
          type: object
57
          properties:
58
            secret32:
59
              type: string
60
            qr_code:
61
              type: string
62
            issuer:
63
              type: string
64
            key_id:
65
              type: string
66
          additionalProperties: false
67
      "400":
68
        description: Bad Request
69
        schema:
70
          $ref: "../swagger.yaml#/definitions/error"
71
      "403":
72
        description: Access forbidden
73
        schema:
74
          $ref: "../swagger.yaml#/definitions/error"
75
      "500":
76
        description: |
77
          Internal server error. Possible `error_code` attribute values:
78
79
          * `internal_server_error`
80
        schema:
81
          $ref: "../swagger.yaml#/definitions/error"
82
    x-koha-authorization:
83
      permissions:
84
        catalogue: "1"
85
86
/auth/two-factor/registration/verification:
87
  post:
88
    x-mojo-to: TwoFactorAuth#verification
89
    operationId: Two factor register verification
90
    tags:
91
      - 2fa
92
    summary: Verify two-factor registration
93
    parameters:
94
      - name: secret32
95
        in: formData
96
        description: the secret
97
        required: true
98
        type: string
99
      - name: pin_code
100
        in: formData
101
        description: the pin code
102
        required: true
103
        type: string
104
    produces:
105
      - application/json
106
    responses:
107
      "204":
108
        description: OK
109
      "401":
110
        description: Authentication required
111
        schema:
112
          $ref: "../swagger.yaml#/definitions/error"
113
      "400":
114
        description: Bad Request
115
        schema:
116
          $ref: "../swagger.yaml#/definitions/error"
117
      "403":
118
        description: Access forbidden
119
        schema:
120
          $ref: "../swagger.yaml#/definitions/error"
121
      "500":
122
        description: |
123
          Internal server error. Possible `error_code` attribute values:
124
125
          * `internal_server_error`
126
        schema:
127
          $ref: "../swagger.yaml#/definitions/error"
128
    x-koha-authorization:
129
      permissions:
130
        catalogue: "1"
(-)a/api/v1/swagger/swagger.yaml (+4 lines)
Lines 103-108 paths: Link Here
103
    $ref: "./paths/article_requests.yaml#/~1article_requests~1{article_request_id}"
103
    $ref: "./paths/article_requests.yaml#/~1article_requests~1{article_request_id}"
104
  /auth/otp/token_delivery:
104
  /auth/otp/token_delivery:
105
    $ref: paths/auth.yaml#/~1auth~1otp~1token_delivery
105
    $ref: paths/auth.yaml#/~1auth~1otp~1token_delivery
106
  /auth/two-factor/registration:
107
    $ref: paths/auth.yaml#/~1auth~1two-factor~1registration
108
  /auth/two-factor/registration/verification:
109
    $ref: paths/auth.yaml#/~1auth~1two-factor~1registration~1verification
106
  "/biblios/{biblio_id}":
110
  "/biblios/{biblio_id}":
107
    $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}"
111
    $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}"
108
  "/biblios/{biblio_id}/checkouts":
112
  "/biblios/{biblio_id}/checkouts":
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt (-1 / +50 lines)
Lines 17-22 Link Here
17
    [% IF ( nopermission ) %]Access denied[% END %] › Koha
17
    [% IF ( nopermission ) %]Access denied[% END %] › Koha
18
</title>
18
</title>
19
[% INCLUDE 'doc-head-close.inc' %]
19
[% INCLUDE 'doc-head-close.inc' %]
20
[% PROCESS 'auth-two-factor.inc' %]
20
</head>
21
</head>
21
<body id="main_auth" class="main_main-auth">
22
<body id="main_auth" class="main_main-auth">
22
23
Lines 71-77 Link Here
71
<p><a href="[% shibbolethLoginUrl | $raw %]">Log in using a Shibboleth account</a>.</p>
72
<p><a href="[% shibbolethLoginUrl | $raw %]">Log in using a Shibboleth account</a>.</p>
72
[% END %]
73
[% END %]
73
74
74
[% IF !TwoFA_prompt && !Koha.Preference('staffShibOnly') %]
75
[% IF !TwoFA_prompt && !TwoFA_setup && !Koha.Preference('staffShibOnly') %]
75
    <!-- login prompt time-->
76
    <!-- login prompt time-->
76
    <form action="[% script_name | html %]" method="post" name="loginform" id="loginform">
77
    <form action="[% script_name | html %]" method="post" name="loginform" id="loginform">
77
        <input type="hidden" name="koha_login_context" value="intranet" />
78
        <input type="hidden" name="koha_login_context" value="intranet" />
Lines 168-173 Link Here
168
        </p>
169
        </p>
169
170
170
    </form>
171
    </form>
172
[% ELSIF TwoFA_setup %]
173
    [% PROCESS registration_form %]
171
[% END %]
174
[% END %]
172
175
173
[% IF ( nopermission ) %]
176
[% IF ( nopermission ) %]
Lines 216-221 Link Here
216
                    });
219
                    });
217
                [% END %]
220
                [% END %]
218
            });
221
            });
222
223
            if( $("#registration-form").length ) {
224
                $.ajax({
225
                    data: {},
226
                    type: 'POST',
227
                    url: '/api/v1/auth/two-factor/registration',
228
                    success: function (data) {
229
                        $("#qr_code").attr('src', data.qr_code);
230
                        $("#secret32").val(data.secret32);
231
                        $("#issuer").html(data.issuer);
232
                        $("#key_id").html(data.key_id);
233
                        $("#registration-form").show();
234
                    },
235
                    error: function (data) {
236
                        alert(data);
237
                    },
238
                });
239
            };
240
241
            $("#register-2FA").on("click", function(e){
242
                e.preventDefault();
243
                const data = {
244
                    secret32: $("#secret32").val(),
245
                    pin_code: $("#pin_code").val(),
246
                };
247
                if (!data.pin_code) return;
248
249
                $.ajax({
250
                    data: data,
251
                    type: 'POST',
252
                    url: '/api/v1/auth/two-factor/registration/verification',
253
                    success: function (data) {
254
                        alert(_("Two-factor authentication correctly configured. You will be redirected to the login screen."));
255
                        window.location = "/cgi-bin/koha/mainpage.pl";
256
                    },
257
                    error: function (data) {
258
                        const error = data.responseJSON.error;
259
                        if ( error == 'Invalid pin' ) {
260
                            $("#errors").html(_("Invalid PIN code")).show();
261
                        } else {
262
                            alert(error);
263
                        }
264
                    },
265
                });
266
            });
267
219
        });
268
        });
220
    </script>
269
    </script>
221
[% END %]
270
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/two_factor_auth.tt (-46 / +64 lines)
Lines 8-14 Link Here
8
</head>
8
</head>
9
<body id="pat_two_factor_auth" class="pat">
9
<body id="pat_two_factor_auth" class="pat">
10
[% INCLUDE 'header.inc' %]
10
[% INCLUDE 'header.inc' %]
11
[% INCLUDE 'patron-search.inc' %]
11
[% PROCESS 'auth-two-factor.inc' %]
12
12
13
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
13
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
14
    <ol>
14
    <ol>
Lines 42-84 Link Here
42
                        </p>
42
                        </p>
43
                    </div>
43
                    </div>
44
                [% ELSE %]
44
                [% ELSE %]
45
                    [% PROCESS registration_form %]
45
46
46
                    [% IF op == 'register' %]
47
                    <div id="registration-status">
47
                        <div class="dialog message">
48
                            <p>We recommend cloud-based mobile authenticator apps such as Authy, Duo Mobile, and LastPass. They can restore access if you lose your hardware device.</p>
49
                            <p>Can't scan the code?</p>
50
                            <p>To add the entry manually, provide the following details to the application on your phone.</p>
51
                            <p>Account: [% issuer | html %]</p>
52
                            <p>Key: [% key_id | html %]</p>
53
                            <p>Time based: Yes</p>
54
                        </div>
55
56
                        [% IF invalid_pin %]
57
                            <div class="dialog error">Invalid PIN code</div>
58
                        [% END %]
59
                        <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
60
                            <fieldset class="rows">
61
                                <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" />
62
                                <input type="hidden" name="op" value="register-2FA" />
63
                                <input type="hidden" name="secret32" value="[% secret32 | html %]" />
64
                                <ol>
65
                                    <li>
66
                                        <label for="qr_code">QR code: </label>
67
                                        <img id="qr_code" src="[% qr_code | $raw %]" />
68
                                    </li>
69
                                    <li>
70
                                        <label for="pin_code">PIN code: </label>
71
                                        <input type="text" id="pin_code" name="pin_code" value="" />
72
                                    </li>
73
                                </ol>
74
                            </fieldset>
75
                            <fieldset class="action">
76
                                <input type="submit" value="Register with two-factor app" />
77
                                <a class="cancel" href="/cgi-bin/koha/members/two_factor_auth.pl">Cancel</a>
78
                            </fieldset>
79
                        </form>
80
                    [% ELSE %]
81
                        [% IF patron.auth_method == "two-factor" %]
48
                        [% IF patron.auth_method == "two-factor" %]
49
                        <div id="registration-status-enabled">
50
                        [% ELSE %]
51
                        <div id="registration-status-enabled" style="display: none;">
52
                        [% END %]
82
                            <div class="two-factor-status">Status: Enabled</div>
53
                            <div class="two-factor-status">Status: Enabled</div>
83
54
84
                            <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
55
                            <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
Lines 86-102 Link Here
86
                                <input type="hidden" name="op" value="disable-2FA" />
57
                                <input type="hidden" name="op" value="disable-2FA" />
87
                                <input type="submit" value="Disable two-factor authentication" />
58
                                <input type="submit" value="Disable two-factor authentication" />
88
                            </form>
59
                            </form>
60
                        </div>
61
62
                        [% IF patron.auth_method == "password" %]
63
                        <div id="registration-status-disabled">
89
                        [% ELSE %]
64
                        [% ELSE %]
65
                        <div id="registration-status-disabled" style="display: none;">
66
                        [% END %]
90
                            <div class="two-factor-status">Status: Disabled</div>
67
                            <div class="two-factor-status">Status: Disabled</div>
68
                            [% IF Koha.Preference('TwoFactorAuthentication') == 'enforced' %]
69
                                <div>Two-factor authentication is mandatory to login. If you do not enable now it will be asked at your next login.</div>
70
                            [% END %]
91
71
92
                            <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
72
                            <input id="enable-2FA" type="submit" value="Enable two-factor authentication" />
93
                                <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" />
73
                        </div>
94
                                <input type="hidden" name="op" value="enable-2FA" />
74
                    </div>
95
                                <input type="submit" value="Enable two-factor authentication" />
96
                            </form>
97
98
                        [% END %]
99
                    [% END %]
100
                [% END %]
75
                [% END %]
101
            </main>
76
            </main>
102
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
77
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
Lines 114-122 Link Here
114
    [% Asset.js("js/members-menu.js") | $raw %]
89
    [% Asset.js("js/members-menu.js") | $raw %]
115
    <script>
90
    <script>
116
        $(document).ready(function(){
91
        $(document).ready(function(){
117
            $(".delete").on("click", function(e){
92
            $("#enable-2FA").on("click", function(e){
118
                return confirmDelete(_("Are you sure you want to delete this key?"));
93
                e.preventDefault();
94
                $.ajax({
95
                    data: {},
96
                    type: 'POST',
97
                    url: '/api/v1/auth/two-factor/registration',
98
                    success: function (data) {
99
                        $("#qr_code").attr('src', data.qr_code);
100
                        $("#secret32").val(data.secret32);
101
                        $("#issuer").html(data.issuer);
102
                        $("#key_id").html(data.key_id);
103
                        $("#registration-form").show();
104
                        $("#registration-status").hide();
105
                    },
106
                    error: function (data) {
107
                        alert(data);
108
                    },
109
                });
119
            });
110
            });
111
112
            $("#register-2FA").on("click", function(e){
113
                e.preventDefault();
114
                const data = {
115
                    secret32: $("#secret32").val(),
116
                    pin_code: $("#pin_code").val(),
117
                };
118
                if (!data.pin_code) return;
119
120
                $.ajax({
121
                    data: data,
122
                    type: 'POST',
123
                    url: '/api/v1/auth/two-factor/registration/verification',
124
                    success: function (data) {
125
                        window.location = "/cgi-bin/koha/members/two_factor_auth.pl";
126
                    },
127
                    error: function (data) {
128
                        const error = data.responseJSON.error;
129
                        if ( error == 'Invalid pin' ) {
130
                            $("#errors").html(_("Invalid PIN code")).show();
131
                        } else {
132
                            alert(error);
133
                        }
134
                    },
135
                });
136
            });
137
120
        });
138
        });
121
    </script>
139
    </script>
122
[% END %]
140
[% END %]
(-)a/members/two_factor_auth.pl (-65 / +1 lines)
Lines 56-125 else { Link Here
56
        token      => scalar $cgi->param('csrf_token'),
56
        token      => scalar $cgi->param('csrf_token'),
57
    };
57
    };
58
58
59
    if ( $op eq 'register-2FA' ) {
59
    if ( $op eq 'disable-2FA' ) {
60
        output_and_exit( $cgi, $cookie, $template, 'wrong_csrf_token' )
61
          unless Koha::Token->new->check_csrf($csrf_pars);
62
63
        my $pin_code = $cgi->param('pin_code');
64
        my $secret32 = $cgi->param('secret32');
65
        my $auth     = Koha::Auth::TwoFactorAuth->new(
66
            { patron => $logged_in_user, secret32 => $secret32 } );
67
68
        my $verified = $auth->verify(
69
            $pin_code,
70
            1,        # range
71
            $secret32,
72
            undef,    # timestamp (defaults to now)
73
            30,       # interval (default 30)
74
        );
75
76
        if ($verified) {
77
78
            # FIXME Generate a (new?) secret
79
            $logged_in_user->encode_secret($secret32);
80
            $logged_in_user->auth_method('two-factor')->store;
81
            $op = 'registered';
82
            if ( $logged_in_user->notice_email_address ) {
83
                $logged_in_user->queue_notice(
84
                    {
85
                        letter_params => {
86
                            module      => 'members',
87
                            letter_code => '2FA_ENABLE',
88
                            branchcode  => $logged_in_user->branchcode,
89
                            lang        => $logged_in_user->lang,
90
                            tables      => {
91
                                branches  => $logged_in_user->branchcode,
92
                                borrowers => $logged_in_user->id
93
                            },
94
                        },
95
                        message_transports => ['email'],
96
                    }
97
                );
98
            }
99
        }
100
        else {
101
            $template->param( invalid_pin => 1, );
102
            $op = 'enable-2FA';
103
        }
104
    }
105
106
    if ( $op eq 'enable-2FA' ) {
107
        my $secret = Koha::AuthUtils::generate_salt( 'weak', 16 );
108
        my $auth   = Koha::Auth::TwoFactorAuth->new(
109
            { patron => $logged_in_user, secret => $secret } );
110
111
        $template->param(
112
            issuer   => $auth->issuer,
113
            key_id   => $auth->key_id,
114
            qr_code  => $auth->qr_code,
115
            secret32 => $auth->secret32,
116
117
            # IMPORTANT: get secret32 after qr_code call !
118
        );
119
        $auth->clear;
120
        $op = 'register';
121
    }
122
    elsif ( $op eq 'disable-2FA' ) {
123
        output_and_exit( $cgi, $cookie, $template, 'wrong_csrf_token' )
60
        output_and_exit( $cgi, $cookie, $template, 'wrong_csrf_token' )
124
          unless Koha::Token->new->check_csrf($csrf_pars);
61
          unless Koha::Token->new->check_csrf($csrf_pars);
125
        my $auth =
62
        my $auth =
126
- 

Return to bug 30588