Revision 8b4dd266
Von Moritz Bunkus vor mehr als 2 Jahren hinzugefügt
SL/BackgroundJob/ValidityTokenCleanup.pm | ||
---|---|---|
1 |
package SL::BackgroundJob::ValidityTokenCleanup; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::BackgroundJob::Base); |
|
6 |
|
|
7 |
use SL::DB::ValidityToken; |
|
8 |
|
|
9 |
sub create_job { |
|
10 |
$_[0]->create_standard_job('0 3 * * *'); # daily |
|
11 |
} |
|
12 |
|
|
13 |
sub run { |
|
14 |
SL::DB::Manager::ValidityToken->cleanup; |
|
15 |
|
|
16 |
return 1; |
|
17 |
} |
|
18 |
|
|
19 |
1; |
|
20 |
|
|
21 |
__END__ |
|
22 |
|
|
23 |
=encoding utf8 |
|
24 |
|
|
25 |
=head1 NAME |
|
26 |
|
|
27 |
SL::BackgroundJob::ValidityTokenCleanup - Background job for |
|
28 |
deleting all expired validity tokens |
|
29 |
|
|
30 |
=cut |
SL/DB/Helper/ALL.pm | ||
---|---|---|
148 | 148 |
use SL::DB::UnitsLanguage; |
149 | 149 |
use SL::DB::UserPreference; |
150 | 150 |
use SL::DB::VC; |
151 |
use SL::DB::ValidityToken; |
|
151 | 152 |
use SL::DB::Vendor; |
152 | 153 |
use SL::DB::Warehouse; |
153 | 154 |
|
SL/DB/Helper/Mappings.pm | ||
---|---|---|
226 | 226 |
units => 'unit', |
227 | 227 |
units_language => 'units_language', |
228 | 228 |
user_preferences => 'user_preference', |
229 |
validity_tokens => 'ValidityToken', |
|
229 | 230 |
vendor => 'vendor', |
230 | 231 |
warehouse => 'warehouse', |
231 | 232 |
); |
SL/DB/Manager/ValidityToken.pm | ||
---|---|---|
1 |
package SL::DB::Manager::ValidityToken; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::DB::Helper::Manager); |
|
6 |
|
|
7 |
sub object_class { 'SL::DB::ValidityToken' } |
|
8 |
|
|
9 |
use Carp; |
|
10 |
|
|
11 |
__PACKAGE__->make_manager_methods; |
|
12 |
|
|
13 |
sub cleanup { |
|
14 |
my ($class) = @_; |
|
15 |
$class->delete_all(where => [ valid_until => { lt => DateTime->now_local }]); |
|
16 |
} |
|
17 |
|
|
18 |
sub fetch_valid_token { |
|
19 |
my ($class, %params) = @_; |
|
20 |
|
|
21 |
croak "missing required parameter 'scope'" if !$params{scope}; |
|
22 |
|
|
23 |
return undef if !$params{token}; |
|
24 |
|
|
25 |
my $token_obj = $class->get_first( |
|
26 |
where => [ |
|
27 |
scope => $params{scope}, |
|
28 |
token => $params{token}, |
|
29 |
valid_until => { ge => DateTime->now_local }, |
|
30 |
]); |
|
31 |
|
|
32 |
return $token_obj; |
|
33 |
} |
|
34 |
|
|
35 |
1; |
SL/DB/MetaSetup/ValidityToken.pm | ||
---|---|---|
1 |
# This file has been auto-generated. Do not modify it; it will be overwritten |
|
2 |
# by rose_auto_create_model.pl automatically. |
|
3 |
package SL::DB::ValidityToken; |
|
4 |
|
|
5 |
use strict; |
|
6 |
|
|
7 |
use parent qw(SL::DB::Object); |
|
8 |
|
|
9 |
__PACKAGE__->meta->table('validity_tokens'); |
|
10 |
|
|
11 |
__PACKAGE__->meta->columns( |
|
12 |
id => { type => 'serial', not_null => 1 }, |
|
13 |
itime => { type => 'timestamp', default => 'now()', not_null => 1 }, |
|
14 |
scope => { type => 'text', not_null => 1 }, |
|
15 |
token => { type => 'text', not_null => 1 }, |
|
16 |
valid_until => { type => 'timestamp', not_null => 1 }, |
|
17 |
); |
|
18 |
|
|
19 |
__PACKAGE__->meta->primary_key_columns([ 'id' ]); |
|
20 |
|
|
21 |
__PACKAGE__->meta->unique_keys([ 'scope', 'token' ]); |
|
22 |
|
|
23 |
__PACKAGE__->meta->allow_inline_column_values(1); |
|
24 |
|
|
25 |
1; |
|
26 |
; |
SL/DB/ValidityToken.pm | ||
---|---|---|
1 |
package SL::DB::ValidityToken; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use Carp; |
|
6 |
use Digest::SHA qw(sha256_hex); |
|
7 |
use Time::HiRes qw(gettimeofday); |
|
8 |
|
|
9 |
use SL::DB::MetaSetup::ValidityToken; |
|
10 |
use SL::DB::Manager::ValidityToken; |
|
11 |
|
|
12 |
__PACKAGE__->meta->initialize; |
|
13 |
|
|
14 |
use constant SCOPE_SALES_INVOICE_POST => 'SalesInvoice::Post'; |
|
15 |
|
|
16 |
sub create { |
|
17 |
my ($class, %params) = @_; |
|
18 |
|
|
19 |
croak "missing required parameter 'scope'" if !$params{scope}; |
|
20 |
|
|
21 |
my $token_obj = $class->new( |
|
22 |
scope => $params{scope}, |
|
23 |
valid_until => $params{valid_until} // DateTime->now_local->add(hours => 24), |
|
24 |
); |
|
25 |
|
|
26 |
while (1) { |
|
27 |
my $token_value = join('-', gettimeofday(), $$, int(rand(1 << 63))); |
|
28 |
|
|
29 |
$token_obj->token(sha256_hex($token_value)); |
|
30 |
|
|
31 |
last if eval { |
|
32 |
$token_obj->save; |
|
33 |
1; |
|
34 |
}; |
|
35 |
} |
|
36 |
|
|
37 |
return $token_obj; |
|
38 |
} |
|
39 |
|
|
40 |
|
|
41 |
1; |
bin/mozilla/is.pl | ||
---|---|---|
52 | 52 |
use SL::DB::Department; |
53 | 53 |
use SL::DB::Invoice; |
54 | 54 |
use SL::DB::PaymentTerm; |
55 |
use SL::DB::ValidityToken; |
|
55 | 56 |
|
56 | 57 |
require "bin/mozilla/common.pl"; |
57 | 58 |
require "bin/mozilla/io.pl"; |
... | ... | |
105 | 106 |
|
106 | 107 |
} |
107 | 108 |
|
109 |
if (!$form->{form_validity_token}) { |
|
110 |
$form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_SALES_INVOICE_POST())->token; |
|
111 |
} |
|
108 | 112 |
|
109 | 113 |
$form->{callback} = "$form->{script}?action=add&type=$form->{type}" unless $form->{callback}; |
110 | 114 |
|
... | ... | |
1008 | 1012 |
$main::auth->assert('invoice_edit'); |
1009 | 1013 |
$form->mtime_ischanged('ar'); |
1010 | 1014 |
|
1015 |
my $validity_token; |
|
1016 |
if (!$form->{id}) { |
|
1017 |
$validity_token = SL::DB::Manager::ValidityToken->fetch_valid_token( |
|
1018 |
scope => SL::DB::ValidityToken::SCOPE_SALES_INVOICE_POST(), |
|
1019 |
token => $form->{form_validity_token}, |
|
1020 |
); |
|
1021 |
|
|
1022 |
$form->error($::locale->text('The form is not valid anymore.')) if !$validity_token; |
|
1023 |
} |
|
1024 |
|
|
1011 | 1025 |
$form->{defaultcurrency} = $form->get_default_currency(\%myconfig); |
1012 | 1026 |
$form->isblank("invdate", $locale->text('Invoice Date missing!')); |
1013 | 1027 |
$form->isblank("customer_id", $locale->text('Customer missing!')); |
... | ... | |
1136 | 1150 |
} |
1137 | 1151 |
} |
1138 | 1152 |
|
1153 |
$validity_token->delete if $validity_token; |
|
1154 |
delete $form->{form_validity_token}; |
|
1155 |
|
|
1139 | 1156 |
if(!exists $form->{addition}) { |
1140 | 1157 |
$form->{snumbers} = 'invnumber' .'_'. $form->{invnumber}; # ($form->{type} eq 'credit_note' ? 'cnnumber' : 'invnumber') .'_'. $form->{invnumber}; |
1141 | 1158 |
$form->{what_done} = 'invoice'; |
locale/de/all | ||
---|---|---|
3641 | 3641 |
'The following transactions are concerned:' => 'Die folgenden Buchungen sind betroffen:', |
3642 | 3642 |
'The following users are a member of this group' => 'Die folgenden Benutzer sind Mitglieder dieser Gruppe', |
3643 | 3643 |
'The following users will have access to this client' => 'Die folgenden Benutzer werden auf diesen Mandanten Zugriff haben', |
3644 |
'The form is not valid anymore.' => 'Das Formular ist nicht mehr gültig.', |
|
3644 | 3645 |
'The formula needs the following syntax:<br>For regular article:<br>Variablename= Variable Unit;<br>Variablename2= Variable2 Unit2;<br>...<br>###<br>Variable + ( Variable2 / Variable )<br><b>Please be beware of the spaces in the formula</b><br>' => 'Die Formeln müssen in der folgenden Syntax eingegeben werden:<br>Bei normalen Artikeln:<br>Variablenname = Variable Einheit;<br>Variablenname2 = Variable2 Einheit2;<br>...<br>###<br>Variable + Variable2 * ( Variable - Variable2 )<br>Variablennamen und Einheiten dürfen nur aus alphanumerischen Zeichen bestehen.<br>Es muss jeweils die Gesamte Zeile eingegeben werden', |
3645 | 3646 |
'The greetings have been saved.' => 'Die Anreden wurden gespeichert', |
3646 | 3647 |
'The installation is currently locked.' => 'Die Installation ist momentan gesperrt.', |
sql/Pg-upgrade2/validity_tokens.sql | ||
---|---|---|
1 |
-- @tag: validity_tokens |
|
2 |
-- @description: Gültigkeits-Tokens z.B. für HTML-Formulare |
|
3 |
-- @depends: release_3_6_1 |
|
4 |
CREATE TABLE validity_tokens ( |
|
5 |
id SERIAL, |
|
6 |
scope TEXT NOT NULL, |
|
7 |
token TEXT NOT NULL, |
|
8 |
itime TIMESTAMP NOT NULL DEFAULT now(), |
|
9 |
valid_until TIMESTAMP NOT NULL, |
|
10 |
|
|
11 |
PRIMARY KEY (id), |
|
12 |
UNIQUE (scope, token) |
|
13 |
); |
|
14 |
|
|
15 |
INSERT INTO background_jobs (type, package_name, cron_spec, next_run_at, active) |
|
16 |
VALUES ('interval', 'ValidityTokenCleanup', '0 3 * * *', now(), true); |
templates/webpages/is/form_header.html | ||
---|---|---|
19 | 19 |
<input type="hidden" name="follow_up_rowcount" id="follow_up_rowcount" value="1"> |
20 | 20 |
<input type="hidden" name="lastmtime" id="lastmtime" value="[% HTML.escape(lastmtime) %]"> |
21 | 21 |
<input type="hidden" name="already_printed_flag" id="already_printed_flag" value="0"> |
22 |
[% IF !id %] |
|
23 |
[% L.hidden_tag('form_validity_token', form_validity_token) %] |
|
24 |
[% END %] |
|
22 | 25 |
|
23 | 26 |
<h1>[% title %]</h1> |
24 | 27 |
|
Auch abrufbar als: Unified diff
proof of concept: Formular-Gültigkeit beim Buchen von Verkaufsrechnungen
Ziel: verhindern, dass eine noch nicht gebuchte Rechnung durch
Verwendung des »Zurück«-Buttons im Browser mehrfach gebucht werden
kann.
Implementation: Beim Neuanlegen einer Rechnung wird ein einmaliges
Gültigkeitstoken mit einem Scope (»Verkaufsrechnung buchen«)
erzeugt. Dieses hat eine Laufzeit von 24h. Es wird als Hidden-Variable
im Formular mitgeschleift, sofern noch keine Datenbank-ID existiert,
die Rechnung also noch nicht gebucht wurde.
Wird die Rechnung gebucht, so wird zuerst überprüft, ob das vom
Browser mitgeschickte Token noch gültig ist:
1. ob es das Token mit demselben Scope in der Datenbank noch gibt
2. ob das Ablaufdatum des Tokens noch nicht erreicht ist
Falls ja, wird die Rechnung gebucht und das Token aus der Datenbank
und dem Formular entfernt. Es ist somit entwertet und kann nicht
erneut zum Buchen verwendet werden, da es bei der Prüfung oben Punkt 1
verletzt.
Falls nein, wird hingegen eine Fehlermeldung angezeigt, dass das
Formular nicht mehr gültig ist. Das kann man auch durch Verwenden von
Funktionen wie »Erneuern« nicht umgehen: das Fomular mit der
ungebuchten Rechnung ist jetzt »tot«.