Dancer2-Plugin-Auth-Extensible

 view release on metacpan or  search on metacpan

lib/Dancer2/Plugin/Auth/Extensible.pm  view on Meta::CPAN

    if ( $module eq 'Mail::Message' ) {

        # require Mail::Message;
        require Mail::Message::Body::String;
        return $plugin->_email_mail_message(@_);
    }
    else {
        croak "No support for $module. Please submit a PR!";
    }
}

sub _return_url {
    my $app = shift;
    my $return_url = $app->request->query_parameters->get('return_url')
        || $app->request->body_parameters->get('return_url')
            or return undef;
    $return_url = uri_unescape($return_url);
    my $uri = URI->new($return_url);
    # Construct a URL using uri_for, which ensures that the correct base domain
    # is used (preventing open URL redirection attacks). The query needs to be
    # parsed and passed as an option, otherwise it is not encoded properly
    return $app->request->uri_for($uri->path, $uri->query_form_hash);
}

#
# routes
#

# implementation of logout route
sub _logout_route {
    my $app = shift;
    my $req = $app->request;
    my $plugin = $app->with_plugin('Auth::Extensible');

    $plugin->execute_plugin_hook( 'before_logout' );

    $app->destroy_session;

    if ( my $url = _return_url($app) ) {
        $app->redirect( $url );
    }
    elsif ($plugin->exit_page) {
        $app->redirect($plugin->exit_page);
    }
    else {
        # TODO: perhaps make this more configurable, perhaps by attempting to
        # render a template first.
        return "OK, logged out successfully.";
    }
}

# implementation of post login route
sub _post_login_route {
    my $app = shift;
    my $plugin = $app->with_plugin('Auth::Extensible');
    my $params = $app->request->body_parameters->as_hashref;

    # First check for password reset request, if applicable
    if (   $plugin->reset_password_handler && $params->{submit_reset} ) {
        my $username = $params->{username_reset};
        croak "Attempt to pass reference to reset blocked" if ref $username;
        $plugin->password_reset_send( username => $username );
        return $app->forward(
            $plugin->login_page,
            { reset_sent => 1 },
            { method     => 'GET' }
        );
    }

    # Then for a password reset itself (confirmed by POST request)
    my ($code) =
         $plugin->reset_password_handler
      && $params->{confirm_reset}
      && $app->request->splat;

    if ($code) {
        no strict 'refs';
        my $randompw = &{ $plugin->password_generator };
        if (my $username = $plugin->user_password( code => $code, new_password => $randompw ) ) {
            # Support a custom 'Change password' page or other app-based
            # intervention after a successful reset code has been applied
            foreach my $realm_check (@{ $plugin->realm_names }) { # $params->{realm} isn't defined at this point...
                my $provider = $plugin->auth_provider($realm_check);
                $params->{realm} = $realm_check if $provider->get_user_details($username);
            }

            $plugin->execute_plugin_hook( 'after_reset_code_success',
                { username => $username, password => $randompw, realm => $params->{realm} } );

            return $app->forward(
                $plugin->login_page,
                { new_password => $randompw },
                { method       => 'GET' }
            );
        }
    }

    # For security, ensure the username and password are straight scalars; if
    # the app is using a serializer and we were sent a blob of JSON, they could
    # have come from that JSON, and thus could be hashrefs (JSON SQL injection)
    # - for database providers, feeding a carefully crafted hashref to the SQL
    # builder could result in different SQL to what we'd expect.
    # For instance, if we pass password => params->{password} to an SQL builder,
    # we'd expect the query to include e.g. "WHERE password = '...'" (likely
    # with paremeterisation) - but if params->{password} was something
    # different, e.g. { 'like' => '%' }, we might end up with some SQL like
    # WHERE password LIKE '%' instead - which would not be a Good Thing.
    my $username = $params->{username} || $params->{__auth_extensible_username};
    my $password = $params->{password} || $params->{__auth_extensible_password};

    for ( $username, $password ) {
        if ( ref $_ ) {

            # TODO: handle more cleanly
            croak "Attempt to pass a reference as username/password blocked";
        }
    }

    if ( $plugin->logged_in_user ) {
        # uncoverable condition false
        $app->redirect( _return_url($app) || $plugin->user_home_page );
    }

    my $auth_realm = $params->{realm} || $params->{__auth_extensible_realm};
    my ( $success, $realm ) =
      $plugin->authenticate_user( $username, $password, $auth_realm );

    if ($success) {

        # change session ID if we have a new enough D2 version with support
        $plugin->app->change_session_id
          if $plugin->app->can('change_session_id');

        $app->session->write( logged_in_user       => $username );
        $app->session->write( logged_in_user_realm => $realm );
        $app->log( core => "Realm is $realm" );
        $plugin->execute_plugin_hook( 'after_login_success' );
        # uncoverable condition false
        $app->redirect( _return_url($app) || $plugin->user_home_page );
    }
    else {
        $app->request->vars->{login_failed}++;
        $app->forward(
            $plugin->login_page,
            { login_failed => 1 },
            { method       => 'GET' }
        );
    }
}

#
# private functions
#

sub _default_password_generator {
    Session::Token->new( length => 8 )->get;
}

sub _reset_code {
    Session::Token->new( length => 32 )->get;
}

# Replacement for much maligned and misunderstood smartmatch operator
sub _smart_match {
    my ( $got, $want ) = @_;
    if ( !ref $want ) {
        return $got eq $want;
    }
    elsif ( ref $want eq 'Regexp' ) {
        return $got =~ $want;
    }
    elsif ( ref $want eq 'ARRAY' ) {
        return grep { $_ eq $got } @$want;
    }
    else {



( run in 2.129 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )