initial commit of file from CVS for smeserver-manager on Fri Mar 22 14:54:28 AEDT 2024
This commit is contained in:
246
root/usr/share/smanager/lib/SrvMngr/Plugin/CSRFDefender.pm
Normal file
246
root/usr/share/smanager/lib/SrvMngr/Plugin/CSRFDefender.pm
Normal file
@@ -0,0 +1,246 @@
|
||||
package SrvMngr::Plugin::CSRFDefender;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Carp;
|
||||
|
||||
our $VERSION = '0.0.8-1';
|
||||
|
||||
use base qw(Mojolicious::Plugin Class::Accessor::Fast);
|
||||
__PACKAGE__->mk_accessors(qw(
|
||||
parameter_name
|
||||
session_key
|
||||
token_length
|
||||
error_status
|
||||
error_content
|
||||
error_template
|
||||
onetime
|
||||
get_token_param
|
||||
|
||||
));
|
||||
|
||||
use String::Random;
|
||||
use Path::Class;
|
||||
|
||||
sub register {
|
||||
my ($self, $app, $conf) = @_;
|
||||
|
||||
# Plugin config
|
||||
$conf ||= {};
|
||||
|
||||
# setting
|
||||
$self->parameter_name($conf->{parameter_name} || 'csrftoken');
|
||||
$self->session_key($conf->{session_key} || 'csrftoken');
|
||||
$self->token_length($conf->{token_length} || 32);
|
||||
$self->error_status($conf->{error_status} || 403);
|
||||
$self->error_content($conf->{error_content} || 'Forbidden');
|
||||
$self->onetime($conf->{onetime} || 0);
|
||||
if ($conf->{error_template}) {
|
||||
my $file = $app->home->rel_file($conf->{error_template});
|
||||
$self->error_template($file);
|
||||
}
|
||||
$self->get_token_param($conf->{get_token_param} || 'CsrfDef=TOKEN'); # added for GET method
|
||||
|
||||
# input check
|
||||
$app->hook(before_dispatch => sub {
|
||||
my ($c) = @_;
|
||||
unless ($self->_validate_csrf($c)) {
|
||||
my $content;
|
||||
if ($self->error_template) {
|
||||
my $file = file($self->error_template);
|
||||
$content = $file->slurp;
|
||||
}
|
||||
else {
|
||||
$content = $self->{error_content},
|
||||
}
|
||||
$c->render(
|
||||
status => $self->{error_status},
|
||||
text => $content,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
# output filter
|
||||
$app->hook(after_dispatch => sub {
|
||||
my ($c) = @_;
|
||||
my $token = $self->_get_csrf_token($c);
|
||||
my $p_name = $self->parameter_name;
|
||||
my $g_token = $self->get_token_param;
|
||||
my $body = $c->res->body;
|
||||
$body =~ s{(<form\s*[^>]*method=["']POST["'][^>]*>)}{$1\n<input type="hidden" name="$p_name" value="$token" />}isg;
|
||||
$body =~ s{(\?$g_token)}{\?$p_name=$token}isg; # added for GET method
|
||||
$c->res->body($body);
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _validate_csrf {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $p_name = $self->parameter_name;
|
||||
my $s_name = $self->session_key;
|
||||
my $request_token = $c->req->param($p_name);
|
||||
my $session_token = $c->session($s_name);
|
||||
|
||||
# POST method or local GET with params.
|
||||
# if ( $c->req->method eq 'POST' or ( $c->req->method eq 'GET' && %{$c->req->params->to_hash} ) ) {
|
||||
# POST method or local GET with param csrftoken present
|
||||
if ( $c->req->method eq 'POST' or ( $c->req->method eq 'GET' && $request_token ) ) {
|
||||
return 0 unless $request_token;
|
||||
return 0 unless $session_token;
|
||||
return 0 unless $request_token eq $session_token;
|
||||
# onetime
|
||||
$c->session($self->{session_key} => '') if $self->onetime;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _get_csrf_token {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $key = $self->session_key;
|
||||
my $token = $c->session($key);
|
||||
my $length = $self->token_length;
|
||||
return $token if $token;
|
||||
|
||||
$token = String::Random::random_regex("[a-zA-Z0-9_]{$length}");
|
||||
$c->session($key => $token);
|
||||
return $token;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Plugin::CSRFDefender - Defend CSRF automatically in Mojolicious Application
|
||||
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
This document describes Mojolicious::Plugin::CSRFDefender.
|
||||
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# Mojolicious
|
||||
$self->plugin('Mojolicious::Plugin::CSRFDefender');
|
||||
|
||||
# Mojolicious::Lite
|
||||
plugin 'Mojolicious::Plugin::CSRFDefender';
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This plugin defends CSRF automatically in Mojolicious Application.
|
||||
Following is the strategy.
|
||||
|
||||
=head2 output filter
|
||||
|
||||
When the application response body contains form tags with method="post",
|
||||
this inserts hidden input tag that contains token string into forms in the response body.
|
||||
For example, the application response body is
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<form method="post" action="/get">
|
||||
<input name="text" />
|
||||
<input type="submit" value="send" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
this becomes
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<form method="post" action="/get">
|
||||
<input type="hidden" name="csrf_token" value="zxjkzX9RnCYwlloVtOVGCfbwjrwWZgWr" />
|
||||
<input name="text" />
|
||||
<input type="submit" value="send" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
=head2 input check
|
||||
|
||||
For every POST requests, this module checks input parameters contain the collect token parameter. If not found, throws 403 Forbidden.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
plugin 'Mojolicious::Plugin::CSRFDefender' => {
|
||||
parameter_name => 'param-csrftoken',
|
||||
session_key => 'session-csrftoken',
|
||||
token_length => 40,
|
||||
error_status => 400,
|
||||
error_template => 'public/400.html',
|
||||
};
|
||||
|
||||
=over 4
|
||||
|
||||
=item parameter_name(default:"csrftoken")
|
||||
|
||||
Name of the input tag for the token.
|
||||
|
||||
=item session_key(default:"csrftoken")
|
||||
|
||||
Name of the session key for the token.
|
||||
|
||||
=item token_length(default:32)
|
||||
|
||||
Length of the token string.
|
||||
|
||||
=item error_status(default:403)
|
||||
|
||||
Status code when CSRF is detected.
|
||||
|
||||
=item error_content(default:"Forbidden")
|
||||
|
||||
Content body when CSRF is detected.
|
||||
|
||||
=item error_template
|
||||
|
||||
Return content of the specified file as content body when CSRF is detected. Specify the file path from the application home directory.
|
||||
|
||||
=item onetime(default:0)
|
||||
|
||||
If specified with 1, this plugin uses onetime token, that is, whenever client sent collect token and this middleware detect that, token string is regenerated.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
L<Mojolicious::Plugin::CSRFDefender> inherits all methods from
|
||||
L<Mojolicious::Plugin> and implements the following new ones.
|
||||
|
||||
=head2 C<register>
|
||||
|
||||
$plugin->register;
|
||||
|
||||
Register plugin in L<Mojolicious> application.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Mojolicious>
|
||||
|
||||
=back
|
||||
|
||||
=head1 REPOSITORY
|
||||
|
||||
https://github.com/shibayu36/p5-Mojolicious-Plugin-CSRFDefender
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
C<< <shibayu36 {at} gmail.com> >>
|
||||
|
||||
|
||||
=head1 LICENCE AND COPYRIGHT
|
||||
|
||||
Copyright (c) 2011, Yuki Shibazaki C<< <shibayu36 {at} gmail.com> >>. All rights reserved.
|
||||
|
||||
This module is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself. See L<perlartistic>.
|
424
root/usr/share/smanager/lib/SrvMngr/Plugin/I18N.pm
Normal file
424
root/usr/share/smanager/lib/SrvMngr/Plugin/I18N.pm
Normal file
@@ -0,0 +1,424 @@
|
||||
package SrvMngr::Plugin::I18N;
|
||||
use Mojo::Base 'Mojolicious::Plugin';
|
||||
|
||||
use Mojo::URL;
|
||||
use I18N::LangTags;
|
||||
use I18N::LangTags::Detect;
|
||||
|
||||
our $VERSION = '1.6';
|
||||
|
||||
# "Can we have Bender burgers again?
|
||||
# No, the cat shelter’s onto me."
|
||||
sub register {
|
||||
my ($plugin, $app, $conf) = @_;
|
||||
|
||||
# Initialize
|
||||
my $namespace = $conf->{namespace} || ( (ref $app) . '::I18N' );
|
||||
my $default = $conf->{default } || 'en';
|
||||
$default =~ tr/-A-Z/_a-z/;
|
||||
$default =~ tr/_a-z0-9//cd;
|
||||
|
||||
my $langs = $conf->{support_url_langs};
|
||||
my $hosts = $conf->{support_hosts };
|
||||
|
||||
# Default Handler
|
||||
my $handler = sub {
|
||||
shift->stash->{i18n} =
|
||||
SrvMngr::Plugin::I18N::_Handler->new(namespace => $namespace, default => $default)
|
||||
;
|
||||
};
|
||||
|
||||
# Add hook
|
||||
$app->hook(
|
||||
before_dispatch => sub {
|
||||
my $self = shift;
|
||||
|
||||
# Handler
|
||||
$handler->( $self );
|
||||
|
||||
# Header detection
|
||||
my @languages = $conf->{no_header_detect}
|
||||
? ()
|
||||
: I18N::LangTags::implicate_supers(
|
||||
I18N::LangTags::Detect->http_accept_langs(
|
||||
$self->req->headers->accept_language
|
||||
)
|
||||
)
|
||||
;
|
||||
|
||||
# Host detection
|
||||
my $host = $self->req->headers->header('X-Host') || $self->req->headers->host;
|
||||
if ($conf->{support_hosts} && $host) {
|
||||
warn $host;
|
||||
$host =~ s/^www\.//; # hack
|
||||
if (my $lang = $conf->{support_hosts}->{ $host }) {
|
||||
$self->app->log->debug("Found language $lang, Host header is $host");
|
||||
|
||||
unshift @languages, $lang;
|
||||
}
|
||||
}
|
||||
|
||||
# Set default language
|
||||
$self->stash(lang_default => $languages[0]) if $languages[0];
|
||||
|
||||
# URL detection
|
||||
if (my $path = $self->req->url->path) {
|
||||
my $part = $path->parts->[0];
|
||||
|
||||
if ($part && $langs && grep { $part eq $_ } @$langs) {
|
||||
# Ignore static files
|
||||
return if $self->res->code;
|
||||
|
||||
$self->app->log->debug("Found language $part in URL $path");
|
||||
|
||||
unshift @languages, $part;
|
||||
|
||||
# Save lang in stash
|
||||
$self->stash(lang => $part);
|
||||
|
||||
# Clean path
|
||||
shift @{$path->parts};
|
||||
$path->trailing_slash(0);
|
||||
}
|
||||
}
|
||||
|
||||
# Languages
|
||||
$self->languages(@languages, $default);
|
||||
}
|
||||
);
|
||||
|
||||
# Add "i18ns" helper
|
||||
$app->helper(i18ns => sub {
|
||||
my $self = shift;
|
||||
|
||||
$handler->( $self ) unless $self->stash('i18n');
|
||||
|
||||
$self->stash->{i18n}->i18namespace(@_);
|
||||
});
|
||||
|
||||
# Add "languages" helper
|
||||
$app->helper(languages => sub {
|
||||
my $self = shift;
|
||||
|
||||
$handler->( $self ) unless $self->stash('i18n');
|
||||
|
||||
$self->stash->{i18n}->languages(@_);
|
||||
});
|
||||
|
||||
# Add "l" helper
|
||||
$app->helper(l => sub {
|
||||
my $self = shift;
|
||||
|
||||
$handler->( $self ) unless $self->stash('i18n');
|
||||
|
||||
$self->stash->{i18n}->localize(@_);
|
||||
});
|
||||
|
||||
# Reimplement "url_for" helper
|
||||
my $mojo_url_for = *Mojolicious::Controller::url_for{CODE};
|
||||
|
||||
my $i18n_url_for = sub {
|
||||
my $self = shift;
|
||||
my $url = $self->$mojo_url_for(@_);
|
||||
|
||||
# Absolute URL
|
||||
return $url if $url->is_abs;
|
||||
|
||||
# Discard target if present
|
||||
shift if (@_ % 2 && !ref $_[0]) || (@_ > 1 && ref $_[-1]);
|
||||
|
||||
# Unveil params
|
||||
my %params = @_ == 1 ? %{$_[0]} : @_;
|
||||
|
||||
# Detect lang
|
||||
if (my $lang = $params{lang} || $self->stash('lang')) {
|
||||
my $path = $url->path || [];
|
||||
|
||||
# Root
|
||||
if (!$path->[0]) {
|
||||
$path->parts([ $lang ]);
|
||||
}
|
||||
|
||||
# No language detected
|
||||
elsif ( ref $langs ne 'ARRAY' or not scalar grep { $path->contains("/$_") } @$langs ) {
|
||||
unshift @{ $path->parts }, $lang;
|
||||
}
|
||||
}
|
||||
|
||||
$url;
|
||||
};
|
||||
|
||||
{
|
||||
no strict 'refs';
|
||||
no warnings 'redefine';
|
||||
|
||||
*Mojolicious::Controller::url_for = $i18n_url_for;
|
||||
}
|
||||
}
|
||||
|
||||
package SrvMngr::Plugin::I18N::_Handler;
|
||||
use Mojo::Base -base;
|
||||
|
||||
use constant DEBUG => $ENV{MOJO_I18N_DEBUG} || 0;
|
||||
|
||||
# "Robot 1-X, save my friends! And Zoidberg!"
|
||||
sub i18namespace {
|
||||
my $self = shift;
|
||||
|
||||
my ($namespace, $language) = @_;
|
||||
return $self->{namespace} unless $namespace && $language;
|
||||
|
||||
$language =~ s/-/_/g if $language;
|
||||
$language = $self->{language} unless $language;
|
||||
|
||||
# Load Lang Module
|
||||
$self->_load_module($namespace => $language);
|
||||
|
||||
if (my $handle = $namespace->get_handle($language)) {
|
||||
$handle->fail_with(sub { $_[1] });
|
||||
$self->{handle} = $handle;
|
||||
$self->{language} = $handle->language_tag;
|
||||
$self->{namespace} = $namespace;
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub languages {
|
||||
my ($self, @languages) = @_;
|
||||
|
||||
unless (@languages) {
|
||||
my $lang = $self->{language};
|
||||
|
||||
# lang such as en-us
|
||||
$lang =~ s/_/-/g;
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
# Handle
|
||||
my $namespace = $self->{namespace};
|
||||
|
||||
# Load Lang Module
|
||||
$self->_load_module($namespace => $_) for @languages;
|
||||
|
||||
if (my $handle = $namespace->get_handle(@languages)) {
|
||||
$handle->fail_with(sub { $_[1] });
|
||||
$self->{handle} = $handle;
|
||||
$self->{language} = $handle->language_tag;
|
||||
$self->{namespace} = $namespace;
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub localize {
|
||||
my $self = shift;
|
||||
my $key = shift;
|
||||
return $key unless my $handle = $self->{handle};
|
||||
return $handle->maketext($key, @_);
|
||||
}
|
||||
|
||||
sub _load_module {
|
||||
my $self = shift;
|
||||
|
||||
my($namespace, $lang) = @_;
|
||||
return unless $namespace && $lang;
|
||||
|
||||
# lang such as en-us
|
||||
$lang =~ s/-/_/g;
|
||||
|
||||
unless ($namespace->can('new')) {
|
||||
DEBUG && warn("Load default namespace $namespace");
|
||||
|
||||
(my $file = $namespace) =~ s{::|'}{/}g;
|
||||
eval qq(require "$file.pm");
|
||||
|
||||
if ($@) {
|
||||
DEBUG && warn("Create default namespace $namespace");
|
||||
|
||||
eval "package $namespace; use base 'Locale::Maketext'; 1;";
|
||||
die qq/Couldn't initialize I18N default class "$namespace": $@/ if $@;
|
||||
}
|
||||
}
|
||||
|
||||
for ($self->{default}, $lang) {
|
||||
my $module = "${namespace}::$_";
|
||||
unless ($module->can('new')) {
|
||||
DEBUG && warn("Load the I18N class $module");
|
||||
|
||||
(my $file = $module) =~ s{::|'}{/}g;
|
||||
eval qq(require "$file.pm");
|
||||
|
||||
my $default = $self->{default};
|
||||
if ($@ || not eval "\%${module}::Lexicon") {
|
||||
if ($_ eq $default) {
|
||||
DEBUG && warn("Create the I18N class $module");
|
||||
|
||||
eval "package ${module}; use base '$namespace';" . 'our %Lexicon = (_AUTO => 1); 1;';
|
||||
die qq/Couldn't initialize I18N class "$namespace": $@/ if $@;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Mojolicious::Plugin::I18N - Internationalization Plugin for Mojolicious
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# Mojolicious
|
||||
$self->plugin('I18N');
|
||||
% languages 'de';
|
||||
%=l 'hello'
|
||||
|
||||
# Mojolicious::Lite (detect language from URL, i.e. /en/ or /de/)
|
||||
plugin I18N => {namespace => 'MyApp::I18N', support_url_langs => [qw(en de)]};
|
||||
%=l 'hello'
|
||||
|
||||
# Lexicon
|
||||
package MyApp::I18N::de;
|
||||
use Mojo::Base 'MyApp::I18N';
|
||||
|
||||
our %Lexicon = (hello => 'hallo');
|
||||
|
||||
1;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
L<Mojolicious::Plugin::I18N> is internationalization plugin for Mojolicious
|
||||
It works with Mojolicious 4.0+.
|
||||
|
||||
Old namespace is L<Mojolicious::Plugin::I18N2>.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
L<Mojolicious::Plugin::I18N> supports the following options.
|
||||
|
||||
=head2 C<support_url_langs>
|
||||
|
||||
plugin I18N => {support_url_langs => [qw(en de)]};
|
||||
|
||||
Detect language from URL.
|
||||
|
||||
=head2 C<support_hosts>
|
||||
|
||||
plugin I18N => {support_hosts => { 'mojolicious.ru' => 'ru', 'mojolicio.us' => 'en' }};
|
||||
|
||||
Detect Host header and use language for that host.
|
||||
|
||||
=head2 C<no_header_detect>
|
||||
|
||||
plugin I18N => {no_header_detect => 1};
|
||||
|
||||
Off header detect.
|
||||
|
||||
=head2 C<default>
|
||||
|
||||
plugin I18N => {default => 'en'};
|
||||
|
||||
Default language for i18n, defaults to C<en>.
|
||||
|
||||
=head2 C<namespace>
|
||||
|
||||
plugin I18N => {namespace => 'MyApp::I18N'};
|
||||
|
||||
Lexicon namespace, defaults to the application class followed by C<::I18N>.
|
||||
|
||||
=head1 HELPERS
|
||||
|
||||
L<Mojolicious::Plugin::I18N> implements helpers same as L<Mojolicious::Plugin::I18N>.
|
||||
|
||||
=head2 C<l>
|
||||
|
||||
%=l 'hello'
|
||||
$self->l('hello');
|
||||
|
||||
Translate sentence.
|
||||
|
||||
=head2 C<languages>
|
||||
|
||||
% languages 'de';
|
||||
$self->languages('de');
|
||||
|
||||
Change languages.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
L<Mojolicious::Plugin::I18N> inherits all methods from L<Mojolicious::Plugin::I18N>
|
||||
and reimplements the following new ones.
|
||||
|
||||
=head2 C<register>
|
||||
|
||||
$plugin->register;
|
||||
|
||||
Register plugin hooks and helpers in L<Mojolicious> application.
|
||||
|
||||
=head1 DEBUG MODE
|
||||
|
||||
L<Mojolicious::Plugin::I18N> has debug mode.
|
||||
|
||||
# debug mode on
|
||||
BEGIN { $ENV{MOJO_I18N_DEBUG} = 1 };
|
||||
|
||||
# or
|
||||
MOJO_I18N_DEBUG=1 perl script.pl
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
2011-2014 Anatoly Sharifulin <sharifulin@gmail.com>
|
||||
|
||||
2010-2012 Sebastian Riedel <kraihx@googlemail.com>
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Please report any bugs or feature requests to C<bug-mojolicious-plugin-i18n at rt.cpan.org>, or through
|
||||
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.htMail?Queue=Mojolicious-Plugin-I18N>. We will be notified, and then you'll
|
||||
automatically be notified of progress on your bug as we make changes.
|
||||
|
||||
=over 5
|
||||
|
||||
=item * Github
|
||||
|
||||
L<http://github.com/sharifulin/mojolicious-plugin-i18n/tree/master>
|
||||
|
||||
=item * RT: CPAN's request tracker
|
||||
|
||||
L<http://rt.cpan.org/NoAuth/Bugs.htMail?Dist=Mojolicious-Plugin-I18N>
|
||||
|
||||
=item * AnnoCPAN: Annotated CPAN documentation
|
||||
|
||||
L<http://annocpan.org/dist/Mojolicious-Plugin-I18N>
|
||||
|
||||
=item * CPANTS: CPAN Testing Service
|
||||
|
||||
L<http://cpants.perl.org/dist/overview/Mojolicious-Plugin-I18N>
|
||||
|
||||
=item * CPAN Ratings
|
||||
|
||||
L<http://cpanratings.perl.org/d/Mojolicious-Plugin-I18N>
|
||||
|
||||
=item * Search CPAN
|
||||
|
||||
L<http://search.cpan.org/dist/Mojolicious-Plugin-I18N>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT & LICENSE
|
||||
|
||||
Copyright (C) 2011-2014 by Anatoly Sharifulin.
|
||||
Copyright (C) 2008-2012, Sebastian Riedel.
|
||||
|
||||
This program is free software, you can redistribute it and/or modify it under
|
||||
the terms of the Artistic License version 2.0.
|
||||
|
||||
=cut
|
Reference in New Issue
Block a user