App-CamelPKI

 view release on metacpan or  search on metacpan

lib/App/CamelPKI/RestrictedClassMethod.pm  view on Meta::CPAN

construction of objects that use the ambiant autority of the process,
are kept secure in capability discipline style.

=head1 CAPABILITY DISCIPLINE

An instance of I<App::CamelPKI::RestrictedClassMethod> represents the right
to invoke methods marked as C<Restricted> in an given Perl class.

=cut

use Class::Inspector;
use App::CamelPKI::Error;

=head1 "use" form

The L</SYNOPSIS> formula

    use App::CamelPKI::RestrictedClassMethod ":Restricted";

indicates that the calling package wants to use the C<Restricted>
attribute for its methods.  When affixed the C<Restricted> on a method
class (again as shown in the synopsis) prevents the execution of this
method to all excepted to holder of the corresponding
I<App::CamelPKI::RestrictedClassMethod> object (see L</METHODS>).

=cut

sub import {
    my ($class, @args) = @_;
    return if ! @args;
    die "unsupported import form" unless (@args == 1 &&
                                          $args[0] eq ":Restricted");
    my ($caller) = caller;
    no strict "refs";
    *{$caller . "::MODIFY_CODE_ATTRIBUTES"} = sub {
        my ($package, $coderef, @attrs) = @_;
        return @attrs unless (@attrs == 1 && $attrs[0] eq "Restricted");
        __PACKAGE__->_create($package)->
            _register_restricted_constructor($coderef);
        return;
    };
}

=head1 CONSTRUCTORS

=head2 grab($classname)

Commences an hostile takeover on $classname. I<grab> will only succeed
once on any given $classname during the lifetime of the Perl
interpreter; when it succeeds, it returns an instance of the
I<App::CamelPKI::RestrictedClassMethod> class which represents the right to
invoke methods marked as C<Restricted> in $classname.

=cut

sub grab {
    my ($class, $wantclass) = @_;
    # Can also be invoked as an instance method from inside this
    # package:
    my $self = ref($class) ? $class : $class->_get($wantclass);
    throw App::CamelPKI::Error::Privilege("$wantclass is not loaded yet")
        if (! defined $self);
    throw App::CamelPKI::Error::Privilege("$wantclass is already taken")
        if ($self->{grabbed});
    $self->lockdown();
    $self->{grabbed}++;
    return $self;
}

=head2 fake_grab($classname)

Returns an object of class
L</App::CamelPKI::RestrictedClassMethod::FakeBrand>.  Unlike the real
L</grab>, a C<fake_grab()> has no security consequences: restricted
methods are not locked down (see L</lockdown>), and C<fake_grab()> may
succeed several times for the same $classname.

=cut

sub fake_grab {
    my ($class, $wantclass) = @_;
    return bless { class => $wantclass },
        "App::CamelPKI::RestrictedClassMethod::FakeBrand";
}

=head1 CLASS METHODS

=head2 grab_all

=head2 grab_all(@classnames)

Performs a call to L</grab> on all classes which have not yet been
grabbed; returns an associative array ("flat hash") alternating class
names and the corresponding brands. This method is meant to be called
at the end of the application's initialization sequence, so as to
guarantee that there are no restricted constructors lingering out
unprotected.  It is also possible for said initialization sequence to
make use of the return value, and distribute all brands by itself to
the appropriate places; in this case, L</grab> will not be called at
all by application code.

=cut

sub grab_all {
    my ($class) = @_;
    my @retval;
    foreach my  $wantclass ($class->_allpackages) {
        my $brand = $class->_get($wantclass);
        next if ($brand->{grabbed});
        push(@retval, $wantclass, scalar($brand->grab));
    }
    return @retval;
}

=head2 lockdown($classname)

Prevents the restricted class methods in $classname from being called,
but don't L</grab> them just yet.  This is optional, as C<grab()>
performs a lockdown anyway.  This class method is idempotent.

=cut

sub lockdown {
    # Also an instance method (for internal calls from L</grab>)
    my $self = ref($_[0]) ? shift : shift->_create(shift);

    while(my $coderef = shift @{$self->{constructor_refs}}) {
        no strict "refs";
        my $codename;
        foreach (@{Class::Inspector->functions($self->{class})}) {
            $codename = $_, last if
                (*{$self->{class} . "::$_"}{CODE} == $coderef);
        }
        throw App::CamelPKI::Error::Internal("ASSERTION_FAILED")
            if (! $codename);
        $self->{constructors}->{$codename} = $coderef;
        no warnings "redefine";
        *{$self->{class} . "::$codename"} = sub {
            throw App::CamelPKI::Error::Privilege
                ("This constructor is restricted");
        };
    }
    return;
}

=head1 METHODS

=head2 is_fake()

Returns false.  See also
L</App::CamelPKI::RestrictedClassMethod::FakeBrand>.

=cut

sub is_fake { 0 }

=head2 invoke($methname, @args)

Invokes the restricted class method named $methname with @args
arguments in the package guarded by this object (that is, the
$classname that was passed as an argument to L</grab>).

=cut

sub invoke {
    my $self = shift; my $meth = shift; unshift @_, $self->{class};
    goto $self->{constructors}->{$meth};
}

=head1 App::CamelPKI::RestrictedClassMethod::FakeBrand

This ancillary class is for fake brand objects created with
L</fake_grab>.  Instances of the class act somewhat like brands (that
is, they also implement L</invoke>); they are intended for testability
purposes, so that code that uses B<App::CamelPKI::RestrictedClassMethod>
can use fake brands for tests, and real ones for production.

=cut

package App::CamelPKI::RestrictedClassMethod::FakeBrand;

=head2 invoke($method, @args)

Invokes $method with arguments @args directly from the package the
brand was constructed from (ie the C<$class> parameter to
L</fake_grab>).

=cut

sub invoke {
    my $self = shift; my $meth = shift; unshift @_, $self->{class};
    goto $self->{class}->can($meth);
}

=head2 is_fake

Returns true.

=cut



( run in 0.572 second using v1.01-cache-2.11-cpan-437f7b0c052 )