Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pod::Weaver::Section::Contributors author dependency not declared #15

Closed
pdl opened this issue Jul 15, 2015 · 2 comments
Closed

Pod::Weaver::Section::Contributors author dependency not declared #15

pdl opened this issue Jul 15, 2015 · 2 comments
Assignees

Comments

@pdl
Copy link
Contributor

pdl commented Jul 15, 2015

Note that Pod::Weaver::Section::Contributors is optional when using the ContributorsFromGit plugin; it is not demanded by dzil authordeps, but appears to be a requirement to build.

$ dzil build
!!! [NoTabsTests] is deprecated and will be removed in a future release; replace it with [Test::NoTabs]
 at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Class/MOP/Method/Wrapped.pm line 41.
[DZ] beginning to build Plack-Middleware-XSRFBlock
[Git::NextVersion] Bumping version from 0.0.9 to 0.0.10
[@Basic/ExtraTests] rewriting release test xt/release/kwalitee.t
[@Basic/ExtraTests] rewriting release test xt/release/no-tabs.t
[@Basic/ExtraTests] rewriting release test xt/release/pod-coverage.t
[@Basic/ExtraTests] rewriting release test xt/release/pod-syntax.t
Pod::Weaver::Section::Contributors (for section Contributors) does not appear to be installed


Trace begun at /home/dp13/perl5/lib/perl5/Config/MVP/Section.pm line 262
Config::MVP::Section::missing_package('Config::MVP::Section=HASH(0x604aef3a8)', 'Pod::Weaver::Section::Contributors', 'Contributors') called at /home/dp13/perl5/lib/perl5/Config/MVP/Section.pm line 231
Config::MVP::Section::load_package('Config::MVP::Section=HASH(0x604aef3a8)', 'Pod::Weaver::Section::Contributors', 'Contributors') called at /home/dp13/perl5/lib/perl5/Config/MVP/Section.pm line 277
Config::MVP::Section::_BUILD_package_settings('Config::MVP::Section=HASH(0x604aef3a8)') called at /home/dp13/perl5/lib/perl5/Config/MVP/Section.pm line 287
Config::MVP::Section::BUILD('Config::MVP::Section=HASH(0x604aef3a8)', 'HASH(0x604696630)') called at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Class/MOP/Method.pm line 123
Class::MOP::Method::execute('Moose::Meta::Method=HASH(0x6021edad8)', 'Config::MVP::Section=HASH(0x604aef3a8)', 'HASH(0x604696630)') called at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Moose/Object.pm line 52
Moose::Object::BUILDALL('Config::MVP::Section=HASH(0x604aef3a8)', 'HASH(0x604696630)') called at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Moose/Meta/Class.pm line 278
Moose::Meta::Class::new_object('Moose::Meta::Class=HASH(0x6020993a8)', 'HASH(0x604696630)') called at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Moose/Object.pm line 23
Moose::Object::new('Config::MVP::Section', 'HASH(0x604aef3d8)') called at /home/dp13/perl5/lib/perl5/Config/MVP/Assembler.pm line 144
Config::MVP::Assembler::begin_section('Pod::Weaver::Config::Assembler=HASH(0x604aff7e0)', 'Contributors', 'Contributors') called at /home/dp13/perl5/lib/perl5/Config/MVP/Assembler.pm line 187
Config::MVP::Assembler::change_section('Pod::Weaver::Config::Assembler=HASH(0x604aff7e0)', 'Contributors', 'Contributors') called at /home/dp13/perl5/lib/perl5/Config/MVP/Reader/INI.pm line 53
Config::MVP::Reader::INI::INIReader::change_section('Config::MVP::Reader::INI::INIReader=HASH(0x604affba0)', 'Contributors') called at /home/dp13/perl5/lib/perl5/Config/INI/Reader.pm line 111
Config::INI::Reader::read_handle('Config::MVP::Reader::INI::INIReader=HASH(0x604affba0)', 'IO::File=GLOB(0x604aff9a8)') called at /home/dp13/perl5/lib/perl5/Mixin/Linewise/Readers.pm line 103
Mixin::Linewise::Readers::__ANON__ at /home/dp13/perl5/lib/perl5/Config/MVP/Reader/INI.pm line 25
Config::MVP::Reader::INI::read_into_assembler('Config::MVP::Reader::INI=HASH(0x604aff5d0)', './weaver.ini', 'Pod::Weaver::Config::Assembler=HASH(0x604aff7e0)') called at /home/dp13/perl5/lib/perl5/Config/MVP/Reader.pm line 53
Config::MVP::Reader::read_config('Config::MVP::Reader::INI=HASH(0x604aff5d0)', './weaver.ini', 'HASH(0x604afeda8)') called at /home/dp13/perl5/lib/perl5/Config/MVP/Reader/Finder.pm line 115
Config::MVP::Reader::Finder::read_config('Pod::Weaver::Config::Finder=HASH(0x604afe8d0)', './weaver') called at /home/dp13/perl5/lib/perl5/Pod/Weaver.pm line 103
Pod::Weaver::new_from_config('Pod::Weaver', 'HASH(0x6048f8288)', 'HASH(0x604aff690)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Plugin/PodWeaver.pm line 63
Dist::Zilla::Plugin::PodWeaver::weaver('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Plugin/PodWeaver.pm line 147
Dist::Zilla::Plugin::PodWeaver::munge_perl_string('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)', 'HASH(0x6048f9b70)', 'HASH(0x604822b80)') called at /home/dp13/perl5/lib/perl5/Pod/Elemental/PerlMunger.pm line 133
Class::MOP::Class:::around('CODE(0x602e5dcf8)', 'Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)', 'package Plack::Middleware::XSRFBlock;^J^Juse strict;^Juse warnings;^Juse parent \'Plack::Middleware\';^J^Juse Digest::HMAC_SHA1 \'hmac_sha1_hex\';^Juse HTML::Escape qw(escape_html);^Juse HTML::Parser;^Juse HTTP::Status qw(:constants);^J^Juse Plack::Response;^Juse Plack::Util;^Juse Plack::Util::Accessor qw(^J    blocked^J    cookie_expiry_seconds^J    cookie_name^J    cookie_is_session_cookie^J    cookie_options^J    inject_form_input^J    logger^J    meta_tag^J    token_per_request^J    parameter_name^J    header_name^J    secret^J    _token_generator^J);^J^Jsub prepare_app {^J    my $self = shift;^J^J    # this needs a value if we aren\'t given one^J    $self->parameter_name( $self->parameter_name || \'xsrf_token\' );^J^J    # default to 1 so we inject hidden inputs to forms^J    $self->inject_form_input(1) unless defined $self->inject_form_input;^J^J    # store the cookie_name^J    $self->cookie_name( $self->cookie_name || \'PSGI-XSRF-Token\' );^J^J    # cookie is session cookie^J    $self->cookie_is_session_cookie( $self->cookie_is_session_cookie || 0 );^J^J    # extra optional options for the cookie^J    $self->cookie_options( $self->cookie_options || {} );^J^J    # default to one token per session, not one per request^J    $self->token_per_request(^J      ref $self->token_per_request eq \'CODE\'^J      ? $self->token_per_request^J      : sub { !! $self->token_per_request }^J    );^J^J    # default to a cookie life of three hours^J    $self->cookie_expiry_seconds( $self->cookie_expiry_seconds || (3 * 60 * 60) );^J^J    $self->_token_generator(sub{^J        my $data    = rand() . $$ . {} . time;^J        my $key     = "@INC";^J        my $digest  = hmac_sha1_hex($data, $key);^J^J        if (defined $self->secret) {^J            my $sig = hmac_sha1_hex($digest, $self->secret);^J            $digest .= "--$sig";^J        }^J^J        return $digest;^J    });^J}^J^Jsub call {^J    my $self    = shift;^J    my $env     = shift;^J^J    # cache the logger^J    $self->logger($env->{\'psgix.logger\'} || sub { })^J        unless defined $self->logger;^J^J    # we\'ll need the Plack::Request for this request^J    my $request = Plack::Request->new($env);^J^J    # grab the cookie where we store the token^J    my $cookie_value = $request->cookies->{$self->cookie_name};^J^J    # deal with form posts^J    if ($request->method =~ m{^post$}i) {^J        $self->log(info => \'POST submitted\');^J^J        # X- header takes precedence over form fields^J        my $val;^J        $val = $request->header( $self->header_name )^J            if (defined $self->header_name);^J        # fallback to the parameter value^J        $val ||= $request->parameters->{ $self->parameter_name };^J^J        # it\'s not easy to decide if we\'re missing the X- value or the form^J        # value^J        # We can say for certain that if we don\'t have the header_name set^J        # it\'s a missing form parameter^J        # If it is set ... well, either could be missing^J        if (not defined $val and not length $val) {^J            # no X- headers expected^J            return $self->xsrf_detected(^J                { env => $env, msg => \'form field missing\' } )^J              if not defined $self->header_name;^J^J            # X- headers and form data allowed^J            return $self->xsrf_detected(^J                { env => $env, msg => \'xsrf token missing\' } )^J^J        }^J^J        # get the value we expect from the cookie^J        return $self->xsrf_detected( { env => $env, msg => \'cookie missing\' } )^J          unless defined $cookie_value;^J^J        # reject if the form value and the token don\'t match^J        return $self->xsrf_detected( { env => $env, msg => \'invalid token\' } )^J          if $val ne $cookie_value;^J^J        return $self->xsrf_detected( { env => $env,^J                msg => \'invalid signature\' } )^J          if $self->invalid_signature($val);^J    }^J^J    return Plack::Util::response_cb($self->app->($env), sub {^J        my $res = shift;^J^J        # Determine whether to create a new token, based on the request^J        $cookie_value = $self->_token_generator->()^J            if $self->token_per_request->( $request );^J^J        # make it easier to work with the headers^J        my $headers = Plack::Util::headers($res->[1]);^J^J        # we can\'t form-munge anything non-HTML^J        my $ct = $headers->get(\'Content-Type\') || \'\';^J        if($ct !~ m{^text/html}i and $ct !~ m{^application/xhtml[+]xml}i){^J            return $res;^J        }^J^J        # GITHUB ISSUE #12 - set cookie after we\'re happy it\'s HTML^J        # get the token value from:^J        # - cookie value, if it\'s already set^J        # - from the generator, if we don\'t have one yet^J        my $token = $cookie_value ||= $self->_token_generator->();^J^J        my %cookie_expires;^J        unless ( $self->cookie_is_session_cookie ) {^J            $cookie_expires{expires} = time + $self->cookie_expiry_seconds;^J        }^J^J        # we need to add our cookie^J        $self->_set_cookie(^J            $token,^J            $res,^J            path    => \'/\',^J            %cookie_expires,^J        );^J^J        return $res unless $self->inject_form_input;^J^J        # escape token (someone might have tampered with the cookie)^J        $token = escape_html($token);^J^J        # let\'s inject our field+token into the form^J        my @out;^J        my $http_host = $request->uri->host;^J        my $parameter_name = $self->parameter_name;^J^J        my $p = HTML::Parser->new( api_version => 3 );^J^J        $p->handler(default => [\@out , \'@{text}\']),^J^J        # we need *all* tags, otherwise we end up with gibberish as the final^J        # page output^J        # i.e. unless there\'s a better way, we *can not* do^J        #    $p->report_tags(qw/head form/);^J^J        # inject our xSRF information^J        $p->handler(^J            start => sub {^J                my($tag, $attr, $text) = @_;^J                # we never want to throw anything away^J                push @out, $text;^J^J                # for easier comparison^J                $tag = lc($tag);^J^J                # If we found the head tag and we want to add a <meta> tag^J                if( $tag eq \'head\' && $self->meta_tag) {^J                    # Put the csrftoken in a <meta> element in <head>^J                    # So that you can get the token in javascript in your^J                    # App to set in X-CSRF-Token header for all your AJAX^J                    # Requests^J                    push @out,^J                        sprintf(^J                            q{<meta name="%s" content="%s"/>},^J                            $self->meta_tag,^J                            $token^J                        );^J                }^J^J                # If tag isn\'t \'form\' and method isn\'t \'post\' we dont care^J                return unless^J                       defined $tag^J                    && defined $attr->{\'method\'}^J                    && $tag eq \'form\'^J                    && $attr->{\'method\'} =~ /post/i;^J^J                if(^J                    !(^J                        defined $attr^J                            and^J                        exists $attr->{\'action\'}^J                            and^J                        $attr->{\'action\'} =~ m{^https?://([^/:]+)[/:]}^J                            and^J                        defined $http_host^J                            and^J                        $1 ne $http_host^J                    )^J                ) {^J                    push @out,^J                        sprintf(^J                            \'<input type="hidden" name="%s" value="%s" />\',^J                            $parameter_name,^J                            $token^J                        );^J                }^J^J                # TODO: determine xhtml or html?^J                return;^J            },^J            "tagname, attr, text",^J        );^J^J        # we never want to throw anything away^J        $p->handler(^J            default => sub {^J                my($tag, $attr, $text) = @_;^J                push @out, $text;^J            },^J            "tagname, attr, text",^J        );^J^J        my $done;^J        return sub {^J            return if $done;^J^J            if(defined(my $chunk = shift)) {^J                $p->parse($chunk);^J            }^J            else {^J                $p->eof;^J                $done++;^J            }^J            join \'\', splice @out;^J        }^J    });^J}^J^Jsub invalid_signature {^J    my ($self, $value) = @_;^J^J    # we dont use signed cookies^J    return if !defined $self->secret;^J^J    # cookie isn\'t signed^J    my ($token, $signature) = split /--/, $value;^J    return 1 if !defined $signature || $signature eq \'\';^J^J    # signature doesn\'t validate^J    return hmac_sha1_hex($token, $self->secret) ne $signature;^J}^J^Jsub xsrf_detected {^J    my $self    = shift;^J    my $args    = shift;^J    my $env = $args->{env};^J    my $msg = $args->{msg}^J        ? sprintf(\'XSRF detected [%s]\', $args->{msg})^J        : \'XSRF detected\';^J^J    $self->log(error => \'XSRF detected, returning HTTP_FORBIDDEN\');^J^J    if (my $app_for_blocked = $self->blocked) {^J        return $app_for_blocked->($env, $@, app => $self->app);^J    }^J^J    return [^J        HTTP_FORBIDDEN,^J        [ \'Content-Type\' => \'text/plain\', \'Content-Length\' => length($msg) ],^J        [ $msg ]^J    ];^J}^J^Jsub log {^J    my ($self, $level, $msg) = @_;^J    $self->logger->({ level => $level, message => "XSRFBlock: $msg" });^J}^J^J# taken from Plack::Session::State::Cookie^J# there\'s a very good reason why we have to do the cookie setting this way ...^J# I just can\'t explain it clearly right now^Jsub _set_cookie {^J    my($self, $id, $res, %options) = @_;^J^J    # TODO: Do not use Plack::Response^J    my $response = Plack::Response->new(@$res);^J    $response->cookies->{ $self->cookie_name } = +{^J        value => $id,^J        %options,^J        %{ $self->cookie_options },^J    };^J^J    my $final_r = $response->finalize;^J    $res->[1] = $final_r->[1]; # headers^J}^J^J1;^J^J=head1 DESCRIPTION^J^JThis middleware blocks XSRF. You can use this middleware without any^Jmodifications to your application.^J^J=head1 SYNOPSIS^J^JThe simplest way to use the plugin is:^J^J    use Plack::Builder;^J^J    my $app = sub { ... };^J^J    builder {^J        enable \'XSRFBlock\';^J        $app;^J    }^J^JYou may also over-ride any, or all of these values:^J^J    builder {^J        enable \'XSRFBlock\',^J            parameter_name          => \'xsrf_token\',^J            cookie_name             => \'PSGI-XSRF-Token\',^J            cookie_options          => {},^J            cookie_expiry_seconds   => (3 * 60 * 60),^J            token_per_request       => 0,^J            meta_tag                => undef,^J            inject_form_input       => 1,^J            header_name             => undef,^J            secret                  => undef,^J            blocked                 => sub {^J                                        return [ $status, $headers, $body ]^J                                    },^J        ;^J        $app;^J    }^J^J=head1 OPTIONS^J^J=over 4^J^J=item parameter_name (default: \'xsrf_token\')^J^JThe name assigned to the hidden form input containing the token.^J^J=item cookie_name (default: \'PSGI-XSRF-Token\')^J^JThe name of the cookie used to store the token value.^J^J=item cookie_expiry_seconds (default: 3*60*60)^J^JThe expiration time in seconds of the XSRF token^J^J=item cookie_is_session_cookie (default: 0)^J^JIf set to a true value, the XSRF token cookie will be set as a session cookie^Jand C<cookie_expiry_seconds> will be ignored.^J^J=item cookie_options (default: {})^J^JExtra cookie options to be set with the cookie.  This is useful for things like^Jsetting C<HttpOnly> to tell the browser to only send it with HTTP requests,^Jand C<Secure> on the cookie to force the cookie to only be sent on SSL requests.^J^J    builder {^J        enable \'XSRFBlock\', cookie_options => { secure => 1, httponly => 1 };^J    }^J^J=item token_per_request (default: 0)^J^JIf this is true a new token is assigned for each request made (but see below).^J^JThis may make your application more secure, but more susceptible to^Jdouble-submit issues.^J^JIf this is a coderef, the request will be passed to the coderef for evaluation.^JIf the result of the evaluation is a true value, a new token will be assigned.^JThis allows fine-grained control, for example to avoid assigning new tokens when^Jincidental requests are made (e.g. on-page ajax requests).^J^J=item meta_tag (default: undef)^J^JIf this is set, use the value as the name of the meta tag to add to the head^Jsection of output pages.^J^JThis is useful when you are using javascript that requires access to the token^Jvalue for making AJAX requests.^J^J=item inject_form_input (default: 1)^J^JIf this is unset, hidden inputs will not be injected into your forms, and no^JHTML parsing will be done on the page responses.^J^JThis can be useful if you only do AJAX requests, and can utilize headers^Jand/or cookies instead, and not need the extra overhead of processing^Jthe HTML document every time.^J^J=item header_name (default: undef)^J^JIf this is set, use the value as the name of the response heaer that the token^Jcan be sent in. This is useful for non-browser based submissions; e.g.^JJavascript AJAX requests.^J^J=item secret (default: undef)^J^JSigns the cookie with supplied secret (if set).^J^J=item blocked (default: undef)^J^JIf this is set it should be a PSGI application that is returned instead of the^Jdefault HTTP_FORBIDDEN(403) and text/plain response.^J^JThis could be useful if you\'d like to perform some action that\'s more in^Jkeeping with your application - e.g. return a styled error page.^J^J=back^J^J=head1 ERRORS^J^JThe module emits various errors based on the cause of the XSRF detected. The^Jmessages will be of the form C<XSRF detected [reason]>^J^J=over 4^J^J=item form field missing^J^JThe request was submitted but there was no value submitted in the form field^Jspecified by <C$self->parameter_name> [default: xsrf_token]^J^J=item xsrf token missing^J^JThe application has been configured to accept an \'X-\' header and no token^Jvalue was found in either the header or a suitable form field. [default: undef]^J^J=item cookie missing^J^JThere is no cookie with the name specified by C<$self->cookie_name> [default:^JPSGI-XSRF-Token]^J^J=item invalid token^J^JThe cookie token and form value were both submitted correctly but the values^Jdo not match.^J^J=item invalid signature^J^JThe cookies signature is invalid, indicating it was tampered with on the way^Jto the browser.^J^J=back^J^J=head1 EXPLANATION^J^JThis module is similar in nature and intention to^JL<Plack::Middleware::CSRFBlock> but implements the xSRF prevention in a^Jdifferent manner.^J^JThe solution implemented in this module is based on a CodingHorror article -^JL<Preventing CSRF and XSRF Attacks|http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html>.^J^JThe driving comment behind this implementation is from^JL<the Felten and Zeller paper|https://www.eecs.berkeley.edu/~daw/teaching/cs261-f11/reading/csrf.pdf>:^J^J    When a user visits a site, the site should generate a (cryptographically^J    strong) pseudorandom value and set it as a cookie on the user\'s machine.^J    The site should require every form submission to include this pseudorandom^J    value as a form value and also as a cookie value. When a POST request is^J    sent to the site, the request should only be considered valid if the form^J    value and the cookie value are the same.  When an attacker submits a form^J    on behalf of a user, he can only modify the values of the form. An^J    attacker cannot read any data sent from the server or modify cookie^J    values, per the same-origin policy.  This means that while an attacker can^J    send any value he wants with the form, he will be unable to modify or read^J    the value stored in the cookie. Since the cookie value and the form value^J    must be the same, the attacker will be unable to successfully submit a^J    form unless he is able to guess the pseudorandom value.^J^J=head2 What\'s wrong with Plack::Middleware::CSRFBlock?^J^JL<Plack::Middleware::CSRFBlock> is a great module.^JIt does a great job of preventing CSRF behaviour with minimal effort.^J^JHowever when we tried to use it uses the session to store information - which^Jworks well most of the time but can cause issues with session timeouts or^Jremoval (for any number of valid reasons) combined with logging (back) in to^Jthe application in another tab (so as not to interfere with the current^Jscreen/tab state).^J^JTrying to modify the existing module to provide the extra functionality and^Jbehaviour we decided worked better for our use seemed too far reaching to try^Jto force into the existing module.^J^J=head2 FURTHER READING^J^J=over 4^J^J=item * Preventing CSRF and XSRF Attacks^J^JL<http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html>^J^J=item * Preventing Cross Site Request Forgery (CSRF)^J^JL<https://www.golemtechnologies.com/articles/csrf>^J^J=item * Cross-Site Request Forgeries: Exploitation and Prevention [PDF]^J^JL<https://www.eecs.berkeley.edu/~daw/teaching/cs261-f11/reading/csrf.pdf>^J^J=item * Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet^J^JL<https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet>^J^J=back^J^J=head2 SEE ALSO^J^JL<Plack::Middleware::CSRFBlock>,^JL<Plack::Middleware>,^JL<Plack>^J^J=cut^J^J# ABSTRACT: Block XSRF Attacks with minimal changes to your app^J__END__^J# vim: ts=8 sts=4 et sw=4 sr sta^J', 'HASH(0x604822b80)') called at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Class/MOP/Method/Wrapped.pm line 157
Class::MOP::Method::Wrapped::__ANON__('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)', 'package Plack::Middleware::XSRFBlock;^J^Juse strict;^Juse warnings;^Juse parent \'Plack::Middleware\';^J^Juse Digest::HMAC_SHA1 \'hmac_sha1_hex\';^Juse HTML::Escape qw(escape_html);^Juse HTML::Parser;^Juse HTTP::Status qw(:constants);^J^Juse Plack::Response;^Juse Plack::Util;^Juse Plack::Util::Accessor qw(^J    blocked^J    cookie_expiry_seconds^J    cookie_name^J    cookie_is_session_cookie^J    cookie_options^J    inject_form_input^J    logger^J    meta_tag^J    token_per_request^J    parameter_name^J    header_name^J    secret^J    _token_generator^J);^J^Jsub prepare_app {^J    my $self = shift;^J^J    # this needs a value if we aren\'t given one^J    $self->parameter_name( $self->parameter_name || \'xsrf_token\' );^J^J    # default to 1 so we inject hidden inputs to forms^J    $self->inject_form_input(1) unless defined $self->inject_form_input;^J^J    # store the cookie_name^J    $self->cookie_name( $self->cookie_name || \'PSGI-XSRF-Token\' );^J^J    # cookie is session cookie^J    $self->cookie_is_session_cookie( $self->cookie_is_session_cookie || 0 );^J^J    # extra optional options for the cookie^J    $self->cookie_options( $self->cookie_options || {} );^J^J    # default to one token per session, not one per request^J    $self->token_per_request(^J      ref $self->token_per_request eq \'CODE\'^J      ? $self->token_per_request^J      : sub { !! $self->token_per_request }^J    );^J^J    # default to a cookie life of three hours^J    $self->cookie_expiry_seconds( $self->cookie_expiry_seconds || (3 * 60 * 60) );^J^J    $self->_token_generator(sub{^J        my $data    = rand() . $$ . {} . time;^J        my $key     = "@INC";^J        my $digest  = hmac_sha1_hex($data, $key);^J^J        if (defined $self->secret) {^J            my $sig = hmac_sha1_hex($digest, $self->secret);^J            $digest .= "--$sig";^J        }^J^J        return $digest;^J    });^J}^J^Jsub call {^J    my $self    = shift;^J    my $env     = shift;^J^J    # cache the logger^J    $self->logger($env->{\'psgix.logger\'} || sub { })^J        unless defined $self->logger;^J^J    # we\'ll need the Plack::Request for this request^J    my $request = Plack::Request->new($env);^J^J    # grab the cookie where we store the token^J    my $cookie_value = $request->cookies->{$self->cookie_name};^J^J    # deal with form posts^J    if ($request->method =~ m{^post$}i) {^J        $self->log(info => \'POST submitted\');^J^J        # X- header takes precedence over form fields^J        my $val;^J        $val = $request->header( $self->header_name )^J            if (defined $self->header_name);^J        # fallback to the parameter value^J        $val ||= $request->parameters->{ $self->parameter_name };^J^J        # it\'s not easy to decide if we\'re missing the X- value or the form^J        # value^J        # We can say for certain that if we don\'t have the header_name set^J        # it\'s a missing form parameter^J        # If it is set ... well, either could be missing^J        if (not defined $val and not length $val) {^J            # no X- headers expected^J            return $self->xsrf_detected(^J                { env => $env, msg => \'form field missing\' } )^J              if not defined $self->header_name;^J^J            # X- headers and form data allowed^J            return $self->xsrf_detected(^J                { env => $env, msg => \'xsrf token missing\' } )^J^J        }^J^J        # get the value we expect from the cookie^J        return $self->xsrf_detected( { env => $env, msg => \'cookie missing\' } )^J          unless defined $cookie_value;^J^J        # reject if the form value and the token don\'t match^J        return $self->xsrf_detected( { env => $env, msg => \'invalid token\' } )^J          if $val ne $cookie_value;^J^J        return $self->xsrf_detected( { env => $env,^J                msg => \'invalid signature\' } )^J          if $self->invalid_signature($val);^J    }^J^J    return Plack::Util::response_cb($self->app->($env), sub {^J        my $res = shift;^J^J        # Determine whether to create a new token, based on the request^J        $cookie_value = $self->_token_generator->()^J            if $self->token_per_request->( $request );^J^J        # make it easier to work with the headers^J        my $headers = Plack::Util::headers($res->[1]);^J^J        # we can\'t form-munge anything non-HTML^J        my $ct = $headers->get(\'Content-Type\') || \'\';^J        if($ct !~ m{^text/html}i and $ct !~ m{^application/xhtml[+]xml}i){^J            return $res;^J        }^J^J        # GITHUB ISSUE #12 - set cookie after we\'re happy it\'s HTML^J        # get the token value from:^J        # - cookie value, if it\'s already set^J        # - from the generator, if we don\'t have one yet^J        my $token = $cookie_value ||= $self->_token_generator->();^J^J        my %cookie_expires;^J        unless ( $self->cookie_is_session_cookie ) {^J            $cookie_expires{expires} = time + $self->cookie_expiry_seconds;^J        }^J^J        # we need to add our cookie^J        $self->_set_cookie(^J            $token,^J            $res,^J            path    => \'/\',^J            %cookie_expires,^J        );^J^J        return $res unless $self->inject_form_input;^J^J        # escape token (someone might have tampered with the cookie)^J        $token = escape_html($token);^J^J        # let\'s inject our field+token into the form^J        my @out;^J        my $http_host = $request->uri->host;^J        my $parameter_name = $self->parameter_name;^J^J        my $p = HTML::Parser->new( api_version => 3 );^J^J        $p->handler(default => [\@out , \'@{text}\']),^J^J        # we need *all* tags, otherwise we end up with gibberish as the final^J        # page output^J        # i.e. unless there\'s a better way, we *can not* do^J        #    $p->report_tags(qw/head form/);^J^J        # inject our xSRF information^J        $p->handler(^J            start => sub {^J                my($tag, $attr, $text) = @_;^J                # we never want to throw anything away^J                push @out, $text;^J^J                # for easier comparison^J                $tag = lc($tag);^J^J                # If we found the head tag and we want to add a <meta> tag^J                if( $tag eq \'head\' && $self->meta_tag) {^J                    # Put the csrftoken in a <meta> element in <head>^J                    # So that you can get the token in javascript in your^J                    # App to set in X-CSRF-Token header for all your AJAX^J                    # Requests^J                    push @out,^J                        sprintf(^J                            q{<meta name="%s" content="%s"/>},^J                            $self->meta_tag,^J                            $token^J                        );^J                }^J^J                # If tag isn\'t \'form\' and method isn\'t \'post\' we dont care^J                return unless^J                       defined $tag^J                    && defined $attr->{\'method\'}^J                    && $tag eq \'form\'^J                    && $attr->{\'method\'} =~ /post/i;^J^J                if(^J                    !(^J                        defined $attr^J                            and^J                        exists $attr->{\'action\'}^J                            and^J                        $attr->{\'action\'} =~ m{^https?://([^/:]+)[/:]}^J                            and^J                        defined $http_host^J                            and^J                        $1 ne $http_host^J                    )^J                ) {^J                    push @out,^J                        sprintf(^J                            \'<input type="hidden" name="%s" value="%s" />\',^J                            $parameter_name,^J                            $token^J                        );^J                }^J^J                # TODO: determine xhtml or html?^J                return;^J            },^J            "tagname, attr, text",^J        );^J^J        # we never want to throw anything away^J        $p->handler(^J            default => sub {^J                my($tag, $attr, $text) = @_;^J                push @out, $text;^J            },^J            "tagname, attr, text",^J        );^J^J        my $done;^J        return sub {^J            return if $done;^J^J            if(defined(my $chunk = shift)) {^J                $p->parse($chunk);^J            }^J            else {^J                $p->eof;^J                $done++;^J            }^J            join \'\', splice @out;^J        }^J    });^J}^J^Jsub invalid_signature {^J    my ($self, $value) = @_;^J^J    # we dont use signed cookies^J    return if !defined $self->secret;^J^J    # cookie isn\'t signed^J    my ($token, $signature) = split /--/, $value;^J    return 1 if !defined $signature || $signature eq \'\';^J^J    # signature doesn\'t validate^J    return hmac_sha1_hex($token, $self->secret) ne $signature;^J}^J^Jsub xsrf_detected {^J    my $self    = shift;^J    my $args    = shift;^J    my $env = $args->{env};^J    my $msg = $args->{msg}^J        ? sprintf(\'XSRF detected [%s]\', $args->{msg})^J        : \'XSRF detected\';^J^J    $self->log(error => \'XSRF detected, returning HTTP_FORBIDDEN\');^J^J    if (my $app_for_blocked = $self->blocked) {^J        return $app_for_blocked->($env, $@, app => $self->app);^J    }^J^J    return [^J        HTTP_FORBIDDEN,^J        [ \'Content-Type\' => \'text/plain\', \'Content-Length\' => length($msg) ],^J        [ $msg ]^J    ];^J}^J^Jsub log {^J    my ($self, $level, $msg) = @_;^J    $self->logger->({ level => $level, message => "XSRFBlock: $msg" });^J}^J^J# taken from Plack::Session::State::Cookie^J# there\'s a very good reason why we have to do the cookie setting this way ...^J# I just can\'t explain it clearly right now^Jsub _set_cookie {^J    my($self, $id, $res, %options) = @_;^J^J    # TODO: Do not use Plack::Response^J    my $response = Plack::Response->new(@$res);^J    $response->cookies->{ $self->cookie_name } = +{^J        value => $id,^J        %options,^J        %{ $self->cookie_options },^J    };^J^J    my $final_r = $response->finalize;^J    $res->[1] = $final_r->[1]; # headers^J}^J^J1;^J^J=head1 DESCRIPTION^J^JThis middleware blocks XSRF. You can use this middleware without any^Jmodifications to your application.^J^J=head1 SYNOPSIS^J^JThe simplest way to use the plugin is:^J^J    use Plack::Builder;^J^J    my $app = sub { ... };^J^J    builder {^J        enable \'XSRFBlock\';^J        $app;^J    }^J^JYou may also over-ride any, or all of these values:^J^J    builder {^J        enable \'XSRFBlock\',^J            parameter_name          => \'xsrf_token\',^J            cookie_name             => \'PSGI-XSRF-Token\',^J            cookie_options          => {},^J            cookie_expiry_seconds   => (3 * 60 * 60),^J            token_per_request       => 0,^J            meta_tag                => undef,^J            inject_form_input       => 1,^J            header_name             => undef,^J            secret                  => undef,^J            blocked                 => sub {^J                                        return [ $status, $headers, $body ]^J                                    },^J        ;^J        $app;^J    }^J^J=head1 OPTIONS^J^J=over 4^J^J=item parameter_name (default: \'xsrf_token\')^J^JThe name assigned to the hidden form input containing the token.^J^J=item cookie_name (default: \'PSGI-XSRF-Token\')^J^JThe name of the cookie used to store the token value.^J^J=item cookie_expiry_seconds (default: 3*60*60)^J^JThe expiration time in seconds of the XSRF token^J^J=item cookie_is_session_cookie (default: 0)^J^JIf set to a true value, the XSRF token cookie will be set as a session cookie^Jand C<cookie_expiry_seconds> will be ignored.^J^J=item cookie_options (default: {})^J^JExtra cookie options to be set with the cookie.  This is useful for things like^Jsetting C<HttpOnly> to tell the browser to only send it with HTTP requests,^Jand C<Secure> on the cookie to force the cookie to only be sent on SSL requests.^J^J    builder {^J        enable \'XSRFBlock\', cookie_options => { secure => 1, httponly => 1 };^J    }^J^J=item token_per_request (default: 0)^J^JIf this is true a new token is assigned for each request made (but see below).^J^JThis may make your application more secure, but more susceptible to^Jdouble-submit issues.^J^JIf this is a coderef, the request will be passed to the coderef for evaluation.^JIf the result of the evaluation is a true value, a new token will be assigned.^JThis allows fine-grained control, for example to avoid assigning new tokens when^Jincidental requests are made (e.g. on-page ajax requests).^J^J=item meta_tag (default: undef)^J^JIf this is set, use the value as the name of the meta tag to add to the head^Jsection of output pages.^J^JThis is useful when you are using javascript that requires access to the token^Jvalue for making AJAX requests.^J^J=item inject_form_input (default: 1)^J^JIf this is unset, hidden inputs will not be injected into your forms, and no^JHTML parsing will be done on the page responses.^J^JThis can be useful if you only do AJAX requests, and can utilize headers^Jand/or cookies instead, and not need the extra overhead of processing^Jthe HTML document every time.^J^J=item header_name (default: undef)^J^JIf this is set, use the value as the name of the response heaer that the token^Jcan be sent in. This is useful for non-browser based submissions; e.g.^JJavascript AJAX requests.^J^J=item secret (default: undef)^J^JSigns the cookie with supplied secret (if set).^J^J=item blocked (default: undef)^J^JIf this is set it should be a PSGI application that is returned instead of the^Jdefault HTTP_FORBIDDEN(403) and text/plain response.^J^JThis could be useful if you\'d like to perform some action that\'s more in^Jkeeping with your application - e.g. return a styled error page.^J^J=back^J^J=head1 ERRORS^J^JThe module emits various errors based on the cause of the XSRF detected. The^Jmessages will be of the form C<XSRF detected [reason]>^J^J=over 4^J^J=item form field missing^J^JThe request was submitted but there was no value submitted in the form field^Jspecified by <C$self->parameter_name> [default: xsrf_token]^J^J=item xsrf token missing^J^JThe application has been configured to accept an \'X-\' header and no token^Jvalue was found in either the header or a suitable form field. [default: undef]^J^J=item cookie missing^J^JThere is no cookie with the name specified by C<$self->cookie_name> [default:^JPSGI-XSRF-Token]^J^J=item invalid token^J^JThe cookie token and form value were both submitted correctly but the values^Jdo not match.^J^J=item invalid signature^J^JThe cookies signature is invalid, indicating it was tampered with on the way^Jto the browser.^J^J=back^J^J=head1 EXPLANATION^J^JThis module is similar in nature and intention to^JL<Plack::Middleware::CSRFBlock> but implements the xSRF prevention in a^Jdifferent manner.^J^JThe solution implemented in this module is based on a CodingHorror article -^JL<Preventing CSRF and XSRF Attacks|http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html>.^J^JThe driving comment behind this implementation is from^JL<the Felten and Zeller paper|https://www.eecs.berkeley.edu/~daw/teaching/cs261-f11/reading/csrf.pdf>:^J^J    When a user visits a site, the site should generate a (cryptographically^J    strong) pseudorandom value and set it as a cookie on the user\'s machine.^J    The site should require every form submission to include this pseudorandom^J    value as a form value and also as a cookie value. When a POST request is^J    sent to the site, the request should only be considered valid if the form^J    value and the cookie value are the same.  When an attacker submits a form^J    on behalf of a user, he can only modify the values of the form. An^J    attacker cannot read any data sent from the server or modify cookie^J    values, per the same-origin policy.  This means that while an attacker can^J    send any value he wants with the form, he will be unable to modify or read^J    the value stored in the cookie. Since the cookie value and the form value^J    must be the same, the attacker will be unable to successfully submit a^J    form unless he is able to guess the pseudorandom value.^J^J=head2 What\'s wrong with Plack::Middleware::CSRFBlock?^J^JL<Plack::Middleware::CSRFBlock> is a great module.^JIt does a great job of preventing CSRF behaviour with minimal effort.^J^JHowever when we tried to use it uses the session to store information - which^Jworks well most of the time but can cause issues with session timeouts or^Jremoval (for any number of valid reasons) combined with logging (back) in to^Jthe application in another tab (so as not to interfere with the current^Jscreen/tab state).^J^JTrying to modify the existing module to provide the extra functionality and^Jbehaviour we decided worked better for our use seemed too far reaching to try^Jto force into the existing module.^J^J=head2 FURTHER READING^J^J=over 4^J^J=item * Preventing CSRF and XSRF Attacks^J^JL<http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html>^J^J=item * Preventing Cross Site Request Forgery (CSRF)^J^JL<https://www.golemtechnologies.com/articles/csrf>^J^J=item * Cross-Site Request Forgeries: Exploitation and Prevention [PDF]^J^JL<https://www.eecs.berkeley.edu/~daw/teaching/cs261-f11/reading/csrf.pdf>^J^J=item * Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet^J^JL<https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet>^J^J=back^J^J=head2 SEE ALSO^J^JL<Plack::Middleware::CSRFBlock>,^JL<Plack::Middleware>,^JL<Plack>^J^J=cut^J^J# ABSTRACT: Block XSRF Attacks with minimal changes to your app^J__END__^J# vim: ts=8 sts=4 et sw=4 sr sta^J', 'HASH(0x604822b80)') called at /home/dp13/perl5/lib/perl5/cygwin-thread-multi/Class/MOP/Method/Wrapped.pm line 86
Dist::Zilla::Plugin::PodWeaver::munge_perl_string('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)', 'package Plack::Middleware::XSRFBlock;^J^Juse strict;^Juse warnings;^Juse parent \'Plack::Middleware\';^J^Juse Digest::HMAC_SHA1 \'hmac_sha1_hex\';^Juse HTML::Escape qw(escape_html);^Juse HTML::Parser;^Juse HTTP::Status qw(:constants);^J^Juse Plack::Response;^Juse Plack::Util;^Juse Plack::Util::Accessor qw(^J    blocked^J    cookie_expiry_seconds^J    cookie_name^J    cookie_is_session_cookie^J    cookie_options^J    inject_form_input^J    logger^J    meta_tag^J    token_per_request^J    parameter_name^J    header_name^J    secret^J    _token_generator^J);^J^Jsub prepare_app {^J    my $self = shift;^J^J    # this needs a value if we aren\'t given one^J    $self->parameter_name( $self->parameter_name || \'xsrf_token\' );^J^J    # default to 1 so we inject hidden inputs to forms^J    $self->inject_form_input(1) unless defined $self->inject_form_input;^J^J    # store the cookie_name^J    $self->cookie_name( $self->cookie_name || \'PSGI-XSRF-Token\' );^J^J    # cookie is session cookie^J    $self->cookie_is_session_cookie( $self->cookie_is_session_cookie || 0 );^J^J    # extra optional options for the cookie^J    $self->cookie_options( $self->cookie_options || {} );^J^J    # default to one token per session, not one per request^J    $self->token_per_request(^J      ref $self->token_per_request eq \'CODE\'^J      ? $self->token_per_request^J      : sub { !! $self->token_per_request }^J    );^J^J    # default to a cookie life of three hours^J    $self->cookie_expiry_seconds( $self->cookie_expiry_seconds || (3 * 60 * 60) );^J^J    $self->_token_generator(sub{^J        my $data    = rand() . $$ . {} . time;^J        my $key     = "@INC";^J        my $digest  = hmac_sha1_hex($data, $key);^J^J        if (defined $self->secret) {^J            my $sig = hmac_sha1_hex($digest, $self->secret);^J            $digest .= "--$sig";^J        }^J^J        return $digest;^J    });^J}^J^Jsub call {^J    my $self    = shift;^J    my $env     = shift;^J^J    # cache the logger^J    $self->logger($env->{\'psgix.logger\'} || sub { })^J        unless defined $self->logger;^J^J    # we\'ll need the Plack::Request for this request^J    my $request = Plack::Request->new($env);^J^J    # grab the cookie where we store the token^J    my $cookie_value = $request->cookies->{$self->cookie_name};^J^J    # deal with form posts^J    if ($request->method =~ m{^post$}i) {^J        $self->log(info => \'POST submitted\');^J^J        # X- header takes precedence over form fields^J        my $val;^J        $val = $request->header( $self->header_name )^J            if (defined $self->header_name);^J        # fallback to the parameter value^J        $val ||= $request->parameters->{ $self->parameter_name };^J^J        # it\'s not easy to decide if we\'re missing the X- value or the form^J        # value^J        # We can say for certain that if we don\'t have the header_name set^J        # it\'s a missing form parameter^J        # If it is set ... well, either could be missing^J        if (not defined $val and not length $val) {^J            # no X- headers expected^J            return $self->xsrf_detected(^J                { env => $env, msg => \'form field missing\' } )^J              if not defined $self->header_name;^J^J            # X- headers and form data allowed^J            return $self->xsrf_detected(^J                { env => $env, msg => \'xsrf token missing\' } )^J^J        }^J^J        # get the value we expect from the cookie^J        return $self->xsrf_detected( { env => $env, msg => \'cookie missing\' } )^J          unless defined $cookie_value;^J^J        # reject if the form value and the token don\'t match^J        return $self->xsrf_detected( { env => $env, msg => \'invalid token\' } )^J          if $val ne $cookie_value;^J^J        return $self->xsrf_detected( { env => $env,^J                msg => \'invalid signature\' } )^J          if $self->invalid_signature($val);^J    }^J^J    return Plack::Util::response_cb($self->app->($env), sub {^J        my $res = shift;^J^J        # Determine whether to create a new token, based on the request^J        $cookie_value = $self->_token_generator->()^J            if $self->token_per_request->( $request );^J^J        # make it easier to work with the headers^J        my $headers = Plack::Util::headers($res->[1]);^J^J        # we can\'t form-munge anything non-HTML^J        my $ct = $headers->get(\'Content-Type\') || \'\';^J        if($ct !~ m{^text/html}i and $ct !~ m{^application/xhtml[+]xml}i){^J            return $res;^J        }^J^J        # GITHUB ISSUE #12 - set cookie after we\'re happy it\'s HTML^J        # get the token value from:^J        # - cookie value, if it\'s already set^J        # - from the generator, if we don\'t have one yet^J        my $token = $cookie_value ||= $self->_token_generator->();^J^J        my %cookie_expires;^J        unless ( $self->cookie_is_session_cookie ) {^J            $cookie_expires{expires} = time + $self->cookie_expiry_seconds;^J        }^J^J        # we need to add our cookie^J        $self->_set_cookie(^J            $token,^J            $res,^J            path    => \'/\',^J            %cookie_expires,^J        );^J^J        return $res unless $self->inject_form_input;^J^J        # escape token (someone might have tampered with the cookie)^J        $token = escape_html($token);^J^J        # let\'s inject our field+token into the form^J        my @out;^J        my $http_host = $request->uri->host;^J        my $parameter_name = $self->parameter_name;^J^J        my $p = HTML::Parser->new( api_version => 3 );^J^J        $p->handler(default => [\@out , \'@{text}\']),^J^J        # we need *all* tags, otherwise we end up with gibberish as the final^J        # page output^J        # i.e. unless there\'s a better way, we *can not* do^J        #    $p->report_tags(qw/head form/);^J^J        # inject our xSRF information^J        $p->handler(^J            start => sub {^J                my($tag, $attr, $text) = @_;^J                # we never want to throw anything away^J                push @out, $text;^J^J                # for easier comparison^J                $tag = lc($tag);^J^J                # If we found the head tag and we want to add a <meta> tag^J                if( $tag eq \'head\' && $self->meta_tag) {^J                    # Put the csrftoken in a <meta> element in <head>^J                    # So that you can get the token in javascript in your^J                    # App to set in X-CSRF-Token header for all your AJAX^J                    # Requests^J                    push @out,^J                        sprintf(^J                            q{<meta name="%s" content="%s"/>},^J                            $self->meta_tag,^J                            $token^J                        );^J                }^J^J                # If tag isn\'t \'form\' and method isn\'t \'post\' we dont care^J                return unless^J                       defined $tag^J                    && defined $attr->{\'method\'}^J                    && $tag eq \'form\'^J                    && $attr->{\'method\'} =~ /post/i;^J^J                if(^J                    !(^J                        defined $attr^J                            and^J                        exists $attr->{\'action\'}^J                            and^J                        $attr->{\'action\'} =~ m{^https?://([^/:]+)[/:]}^J                            and^J                        defined $http_host^J                            and^J                        $1 ne $http_host^J                    )^J                ) {^J                    push @out,^J                        sprintf(^J                            \'<input type="hidden" name="%s" value="%s" />\',^J                            $parameter_name,^J                            $token^J                        );^J                }^J^J                # TODO: determine xhtml or html?^J                return;^J            },^J            "tagname, attr, text",^J        );^J^J        # we never want to throw anything away^J        $p->handler(^J            default => sub {^J                my($tag, $attr, $text) = @_;^J                push @out, $text;^J            },^J            "tagname, attr, text",^J        );^J^J        my $done;^J        return sub {^J            return if $done;^J^J            if(defined(my $chunk = shift)) {^J                $p->parse($chunk);^J            }^J            else {^J                $p->eof;^J                $done++;^J            }^J            join \'\', splice @out;^J        }^J    });^J}^J^Jsub invalid_signature {^J    my ($self, $value) = @_;^J^J    # we dont use signed cookies^J    return if !defined $self->secret;^J^J    # cookie isn\'t signed^J    my ($token, $signature) = split /--/, $value;^J    return 1 if !defined $signature || $signature eq \'\';^J^J    # signature doesn\'t validate^J    return hmac_sha1_hex($token, $self->secret) ne $signature;^J}^J^Jsub xsrf_detected {^J    my $self    = shift;^J    my $args    = shift;^J    my $env = $args->{env};^J    my $msg = $args->{msg}^J        ? sprintf(\'XSRF detected [%s]\', $args->{msg})^J        : \'XSRF detected\';^J^J    $self->log(error => \'XSRF detected, returning HTTP_FORBIDDEN\');^J^J    if (my $app_for_blocked = $self->blocked) {^J        return $app_for_blocked->($env, $@, app => $self->app);^J    }^J^J    return [^J        HTTP_FORBIDDEN,^J        [ \'Content-Type\' => \'text/plain\', \'Content-Length\' => length($msg) ],^J        [ $msg ]^J    ];^J}^J^Jsub log {^J    my ($self, $level, $msg) = @_;^J    $self->logger->({ level => $level, message => "XSRFBlock: $msg" });^J}^J^J# taken from Plack::Session::State::Cookie^J# there\'s a very good reason why we have to do the cookie setting this way ...^J# I just can\'t explain it clearly right now^Jsub _set_cookie {^J    my($self, $id, $res, %options) = @_;^J^J    # TODO: Do not use Plack::Response^J    my $response = Plack::Response->new(@$res);^J    $response->cookies->{ $self->cookie_name } = +{^J        value => $id,^J        %options,^J        %{ $self->cookie_options },^J    };^J^J    my $final_r = $response->finalize;^J    $res->[1] = $final_r->[1]; # headers^J}^J^J1;^J^J=head1 DESCRIPTION^J^JThis middleware blocks XSRF. You can use this middleware without any^Jmodifications to your application.^J^J=head1 SYNOPSIS^J^JThe simplest way to use the plugin is:^J^J    use Plack::Builder;^J^J    my $app = sub { ... };^J^J    builder {^J        enable \'XSRFBlock\';^J        $app;^J    }^J^JYou may also over-ride any, or all of these values:^J^J    builder {^J        enable \'XSRFBlock\',^J            parameter_name          => \'xsrf_token\',^J            cookie_name             => \'PSGI-XSRF-Token\',^J            cookie_options          => {},^J            cookie_expiry_seconds   => (3 * 60 * 60),^J            token_per_request       => 0,^J            meta_tag                => undef,^J            inject_form_input       => 1,^J            header_name             => undef,^J            secret                  => undef,^J            blocked                 => sub {^J                                        return [ $status, $headers, $body ]^J                                    },^J        ;^J        $app;^J    }^J^J=head1 OPTIONS^J^J=over 4^J^J=item parameter_name (default: \'xsrf_token\')^J^JThe name assigned to the hidden form input containing the token.^J^J=item cookie_name (default: \'PSGI-XSRF-Token\')^J^JThe name of the cookie used to store the token value.^J^J=item cookie_expiry_seconds (default: 3*60*60)^J^JThe expiration time in seconds of the XSRF token^J^J=item cookie_is_session_cookie (default: 0)^J^JIf set to a true value, the XSRF token cookie will be set as a session cookie^Jand C<cookie_expiry_seconds> will be ignored.^J^J=item cookie_options (default: {})^J^JExtra cookie options to be set with the cookie.  This is useful for things like^Jsetting C<HttpOnly> to tell the browser to only send it with HTTP requests,^Jand C<Secure> on the cookie to force the cookie to only be sent on SSL requests.^J^J    builder {^J        enable \'XSRFBlock\', cookie_options => { secure => 1, httponly => 1 };^J    }^J^J=item token_per_request (default: 0)^J^JIf this is true a new token is assigned for each request made (but see below).^J^JThis may make your application more secure, but more susceptible to^Jdouble-submit issues.^J^JIf this is a coderef, the request will be passed to the coderef for evaluation.^JIf the result of the evaluation is a true value, a new token will be assigned.^JThis allows fine-grained control, for example to avoid assigning new tokens when^Jincidental requests are made (e.g. on-page ajax requests).^J^J=item meta_tag (default: undef)^J^JIf this is set, use the value as the name of the meta tag to add to the head^Jsection of output pages.^J^JThis is useful when you are using javascript that requires access to the token^Jvalue for making AJAX requests.^J^J=item inject_form_input (default: 1)^J^JIf this is unset, hidden inputs will not be injected into your forms, and no^JHTML parsing will be done on the page responses.^J^JThis can be useful if you only do AJAX requests, and can utilize headers^Jand/or cookies instead, and not need the extra overhead of processing^Jthe HTML document every time.^J^J=item header_name (default: undef)^J^JIf this is set, use the value as the name of the response heaer that the token^Jcan be sent in. This is useful for non-browser based submissions; e.g.^JJavascript AJAX requests.^J^J=item secret (default: undef)^J^JSigns the cookie with supplied secret (if set).^J^J=item blocked (default: undef)^J^JIf this is set it should be a PSGI application that is returned instead of the^Jdefault HTTP_FORBIDDEN(403) and text/plain response.^J^JThis could be useful if you\'d like to perform some action that\'s more in^Jkeeping with your application - e.g. return a styled error page.^J^J=back^J^J=head1 ERRORS^J^JThe module emits various errors based on the cause of the XSRF detected. The^Jmessages will be of the form C<XSRF detected [reason]>^J^J=over 4^J^J=item form field missing^J^JThe request was submitted but there was no value submitted in the form field^Jspecified by <C$self->parameter_name> [default: xsrf_token]^J^J=item xsrf token missing^J^JThe application has been configured to accept an \'X-\' header and no token^Jvalue was found in either the header or a suitable form field. [default: undef]^J^J=item cookie missing^J^JThere is no cookie with the name specified by C<$self->cookie_name> [default:^JPSGI-XSRF-Token]^J^J=item invalid token^J^JThe cookie token and form value were both submitted correctly but the values^Jdo not match.^J^J=item invalid signature^J^JThe cookies signature is invalid, indicating it was tampered with on the way^Jto the browser.^J^J=back^J^J=head1 EXPLANATION^J^JThis module is similar in nature and intention to^JL<Plack::Middleware::CSRFBlock> but implements the xSRF prevention in a^Jdifferent manner.^J^JThe solution implemented in this module is based on a CodingHorror article -^JL<Preventing CSRF and XSRF Attacks|http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html>.^J^JThe driving comment behind this implementation is from^JL<the Felten and Zeller paper|https://www.eecs.berkeley.edu/~daw/teaching/cs261-f11/reading/csrf.pdf>:^J^J    When a user visits a site, the site should generate a (cryptographically^J    strong) pseudorandom value and set it as a cookie on the user\'s machine.^J    The site should require every form submission to include this pseudorandom^J    value as a form value and also as a cookie value. When a POST request is^J    sent to the site, the request should only be considered valid if the form^J    value and the cookie value are the same.  When an attacker submits a form^J    on behalf of a user, he can only modify the values of the form. An^J    attacker cannot read any data sent from the server or modify cookie^J    values, per the same-origin policy.  This means that while an attacker can^J    send any value he wants with the form, he will be unable to modify or read^J    the value stored in the cookie. Since the cookie value and the form value^J    must be the same, the attacker will be unable to successfully submit a^J    form unless he is able to guess the pseudorandom value.^J^J=head2 What\'s wrong with Plack::Middleware::CSRFBlock?^J^JL<Plack::Middleware::CSRFBlock> is a great module.^JIt does a great job of preventing CSRF behaviour with minimal effort.^J^JHowever when we tried to use it uses the session to store information - which^Jworks well most of the time but can cause issues with session timeouts or^Jremoval (for any number of valid reasons) combined with logging (back) in to^Jthe application in another tab (so as not to interfere with the current^Jscreen/tab state).^J^JTrying to modify the existing module to provide the extra functionality and^Jbehaviour we decided worked better for our use seemed too far reaching to try^Jto force into the existing module.^J^J=head2 FURTHER READING^J^J=over 4^J^J=item * Preventing CSRF and XSRF Attacks^J^JL<http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html>^J^J=item * Preventing Cross Site Request Forgery (CSRF)^J^JL<https://www.golemtechnologies.com/articles/csrf>^J^J=item * Cross-Site Request Forgeries: Exploitation and Prevention [PDF]^J^JL<https://www.eecs.berkeley.edu/~daw/teaching/cs261-f11/reading/csrf.pdf>^J^J=item * Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet^J^JL<https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet>^J^J=back^J^J=head2 SEE ALSO^J^JL<Plack::Middleware::CSRFBlock>,^JL<Plack::Middleware>,^JL<Plack>^J^J=cut^J^J# ABSTRACT: Block XSRF Attacks with minimal changes to your app^J__END__^J# vim: ts=8 sts=4 et sw=4 sr sta^J', 'HASH(0x604822b80)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Plugin/PodWeaver.pm line 163
Dist::Zilla::Plugin::PodWeaver::munge_pod('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)', 'Dist::Zilla::File::OnDisk=HASH(0x6046e49b8)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Plugin/PodWeaver.pm line 140
Dist::Zilla::Plugin::PodWeaver::munge_file('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)', 'Dist::Zilla::File::OnDisk=HASH(0x6046e49b8)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Plugin/PodWeaver.pm line 132
Dist::Zilla::Plugin::PodWeaver::munge_files('Dist::Zilla::Plugin::PodWeaver=HASH(0x602ec3228)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Dist/Builder.pm line 323
Dist::Zilla::Dist::Builder::build_in('Dist::Zilla::Dist::Builder=HASH(0x60233c9b8)', undef) called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Dist/Builder.pm line 389
Dist::Zilla::Dist::Builder::ensure_built_in('Dist::Zilla::Dist::Builder=HASH(0x60233c9b8)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Dist/Builder.pm line 378
Dist::Zilla::Dist::Builder::ensure_built('Dist::Zilla::Dist::Builder=HASH(0x60233c9b8)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/Dist/Builder.pm line 442
Dist::Zilla::Dist::Builder::build_archive('Dist::Zilla::Dist::Builder=HASH(0x60233c9b8)') called at /home/dp13/perl5/lib/perl5/Dist/Zilla/App/Command/build.pm line 71
Dist::Zilla::App::Command::build::execute('Dist::Zilla::App::Command::build=HASH(0x600759308)', 'Getopt::Long::Descriptive::Opts::__OPT__::2=HASH(0x600893550)', 'ARRAY(0x6003d4f38)') called at /home/dp13/perl5/lib/perl5/App/Cmd.pm line 445
App::Cmd::execute_command('Dist::Zilla::App=HASH(0x600276848)', 'Dist::Zilla::App::Command::build=HASH(0x600759308)', 'Getopt::Long::Descriptive::Opts::__OPT__::2=HASH(0x600893550)') called at /home/dp13/perl5/lib/perl5/App/Cmd.pm line 312
App::Cmd::run('Dist::Zilla::App') called at /home/dp13/perl5/bin/dzil line 12
@chizmw chizmw self-assigned this Jul 18, 2015
@chizmw
Copy link
Owner

chizmw commented Jul 18, 2015

Thanks for raising this. I hit the same problem recently when I was setting up a new machine.

I don't know why

[Prereqs / DevelopRequires]
Pod::Weaver::Section::Contributors = 0

didn't pull in the distribution.

After some further research I've decided to take the belt and braces approach by also including a 'magic comment':

[AutoPrereqs]
; authordep Pod::Weaver::Section::Contributors

This was recently pushed to master, but not release.
I'm about to rebase-interactive so that I can reference this issue in the commit summary.

@chizmw chizmw closed this as completed in 070ebf5 Jul 18, 2015
chizmw added a commit that referenced this issue Jul 18, 2015
 - add scripts for BuildKite testing
 - dzil: Replace NoTabsTests with Test::NoTabs
 - fix #15: Use magic comment to crowbar in an essential dependency
 - add magic comments for other author dependencies that aren't auto-detected
 - Add *skeleton* POD for methods
   This is a shockingly lazy way for me to get my dist to pass Pod::Coverage tests
 - re-arrange POD in file; so method POD appears in a sensible location
 - add (markdown) section of POD for build status to show (in github/README.mkdn)
chizmw added a commit that referenced this issue Jul 18, 2015
 - add scripts for BuildKite testing
 - dzil: Replace NoTabsTests with Test::NoTabs
 - fix #15: Use magic comment to crowbar in an essential dependency
 - add magic comments for other author dependencies that aren't auto-detected
 - Add *skeleton* POD for methods
   This is a shockingly lazy way for me to get my dist to pass Pod::Coverage tests
 - re-arrange POD in file; so method POD appears in a sensible location
 - add (markdown) section of POD for build status to show (in github/README.mkdn)
Jayceh added a commit to Jayceh/Business--OnlinePayment--Litle that referenced this issue Dec 18, 2015
@karenetheridge
Copy link
Contributor

I'm not sure what you mean by "didn't pull in the distribution", because dzil build doesn't install any prerequisites for you. That's still up to you to do, e.g. via dzil authordeps | cpanm; dzil listdeps --develop | cpanm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants