use warnings;
use strict;
package Jifty::API;
=head1 NAME
Jifty::API - Manages and allow reflection on the Jifty::Actions that
make up a Jifty application's API
=head1 SYNOPSIS
# Find the full name of an action
my $class = Jifty->api->qualify('SomeAction');
# Logged users with an ID greater than 10 have restrictions
if (Jifty->web->current_user->id > 10) {
Jifty->api->deny('Foo');
Jifty->api->allow('FooBar');
Jifty->api->deny('FooBarDeleteTheWorld');
}
# Fetch the class names of all the allowed actions
my @actions = Jifty->api->actions;
# Check to see if an action is allowed
if (Jifty->api->is_allowed('TrueFooBar')) {
# do something...
}
# Undo all allow/deny/restrict calls
Jifty->api->reset;
=head1 DESCRIPTION
You can fetch an instance of this class by calling L<Jifty/api> in your application. This object can be used to examine the actions available within your application and manage access to those actions.
=cut
use base qw/Class::Accessor::Fast Jifty::Object/;
__PACKAGE__->mk_accessors(qw(action_limits));
=head1 METHODS
=head2 new
Creates a new C<Jifty::API> object.
Don't use this, see L<Jifty/api> to access a reference to C<Jifty::API> in your application.
=cut
sub new {
my $class = shift;
my $self = bless {}, $class;
# Setup the basic allow/deny rules
$self->reset;
# Find all the actions for the API reference (available at _actions)
Jifty::Module::Pluggable->import(
search_path => [
Jifty->app_class("Action"),
"Jifty::Action",
map {ref($_)."::Action"} Jifty->plugins,
],
except => qr/\.#/,
sub_name => "_actions",
);
return ($self);
}
=head2 qualify ACTIONNAME
Returns the fully qualified package name for the given provided
action. If the C<ACTIONNAME> starts with C<Jifty::> or
C<ApplicationClass::Action>, simply returns the given name; otherwise,
it prefixes it with the C<ApplicationClass::Action>.
=cut
sub qualify {
my $self = shift;
my $action = shift;
# Get the application class name
my $base_path = Jifty->app_class;
# Return the class now if it's already fully qualified
return $action
if ($action =~ /^Jifty::/
or $action =~ /^\Q$base_path\E::/);
# Otherwise qualify it
return $base_path . "::Action::" . $action;
}
=head2 reset
Resets which actions are allowed to the defaults; that is, all of the
application's actions, L<Jifty::Action::Autocomplete>, and
L<Jifty::Action::Redirect> are allowed; everything else is denied.
See L</restrict> for the details of how limits are processed.
=cut
sub reset {
my $self = shift;
# Set up defaults
my $app_actions = Jifty->app_class("Action");
# These are the default action limits
$self->action_limits(
[ { deny => 1, restriction => qr/.*/ },
{ allow => 1,
restriction => qr/^\Q$app_actions\E/,
},
{ allow => 1, restriction => 'Jifty::Action::Autocomplete' },
{ allow => 1, restriction => 'Jifty::Action::Redirect' },
]
);
}
=head2 allow RESTRICTIONS
Takes a list of strings or regular expressions, and adds them in order
to the list of limits for the purposes of L</is_allowed>. See
L</restrict> for the details of how limits are processed.
=cut
sub allow {
my $self = shift;
$self->restrict( allow => @_ );
}
=head2 deny RESTRICTIONS
Takes a list of strings or regular expressions, and adds them in order
to the list of limits for the purposes of L</is_allowed>. See
L</restrict> for the details of how limits are processed.
=cut
sub deny {
my $self = shift;
$self->restrict( deny => @_ );
}
=head2 restrict POLARITY RESTRICTIONS
Method that L</allow> and L</deny> call internally; I<POLARITY> is
either C<allow> or C<deny>. Allow and deny limits are evaluated in
the order they're called. The last limit that applies will be the one
which takes effect. Regexes are matched against the class; strings
are fully L</qualify|qualified> and used as an exact match against the
class name. The base set of restrictions (which is reset every
request) is set in L</reset>, and usually modified by the
application's L<Jifty::Dispatcher> if need be.
If you call:
Jifty->api->deny ( qr'Foo' );
Jifty->api->allow ( qr'FooBar' );
Jifty->api->deny ( qr'FooBarDeleteTheWorld' );
..then:
calls to MyApp::Action::Baz will succeed.
calls to MyApp::Action::Foo will fail.
calls to MyApp::Action::FooBar will pass.
calls to MyApp::Action::TrueFoo will fail.
calls to MyApp::Action::TrueFooBar will pass.
calls to MyApp::Action::TrueFooBarDeleteTheWorld will fail.
calls to MyApp::Action::FooBarDeleteTheWorld will fail.
=cut
sub restrict {
my $self = shift;
my $polarity = shift;
my @restrictions = @_;
# Check the sanity of the polarity
die "Polarity must be 'allow' or 'deny'"
unless $polarity eq "allow"
or $polarity eq "deny";
for my $restriction (@restrictions) {
# Don't let the user "allow .*"
die "For security reasons, Jifty won't let you allow all actions"
if $polarity eq "allow"
and ref $restriction
and $restriction =~ /^\(\?[-xism]*:\^?\.\*\$?\)$/;
# Fully qualify it if it's a string
$restriction = $self->qualify($restriction)
unless ref $restriction;
# Add to list of restrictions
push @{ $self->action_limits },
{ $polarity => 1, restriction => $restriction };
}
}
=head2 is_allowed CLASS
Returns true if the I<CLASS> name (which is fully qualified if it is
not already) is allowed to be executed. See L</restrict> above for
the rules that the class name must pass.
=cut
sub is_allowed {
my $self = shift;
my $class = shift;
# Qualify the action
$class = $self->qualify($class);
# Assume that it doesn't pass; however, the real fallbacks are
# controlled by L</reset>, above.
my $allow = 0;
# Walk all of the limits
for my $limit ( @{ $self->action_limits } ) {
# Regexes are =~ matches, strigns are eq matches
if ( ( ref $limit->{restriction} and $class =~ $limit->{restriction} )
or ( $class eq $limit->{restriction} ) )
{
# If the restriction passes, set the current allow/deny
# bit according to if this was a positive or negative
# limit
$allow = $limit->{allow} ? 1 : 0;
}
}
return $allow;
}
=head2 actions
Lists the class names of all of the allowed actions for this Jifty
application; this may include actions under the C<Jifty::Action::>
namespace, in addition to your application's actions.
=cut
sub actions {
my $self = shift;
return sort grep { $self->is_allowed($_) } $self->_actions;
}
=head1 SEE ALSO
L<Jifty>, L<Jifty::Web>, L<Jifty::Action>
=head1 LICENSE
Jifty is Copyright 2005-2006 Best Practical Solutions, LLC.
Jifty is distributed under the same terms as Perl itself.
=cut
1;