+sub IsGroup {
+ my $self = shift;
+ if ( defined $self->PrincipalType &&
+ $self->PrincipalType eq 'Group' ) {
+ return 1;
+ }
+ return undef;
+}
+
+
+
+=head2 IsUser
+
+Returns true if this principal is a User.
+Returns undef, otherwise
+
+=cut
+
+sub IsUser {
+ my $self = shift;
+ if ($self->PrincipalType eq 'User') {
+ return(1);
+ }
+ else {
+ return undef;
+ }
+}
+
+
+
+=head2 Object
+
+Returns the user or group associated with this principal
+
+=cut
+
+sub Object {
+ my $self = shift;
+
+ unless ( $self->{'object'} ) {
+ if ( $self->IsUser ) {
+ $self->{'object'} = RT::User->new($self->CurrentUser);
+ }
+ elsif ( $self->IsGroup ) {
+ $self->{'object'} = RT::Group->new($self->CurrentUser);
+ }
+ else {
+ $RT::Logger->crit("Found a principal (".$self->Id.") that was neither a user nor a group");
+ return(undef);
+ }
+ $self->{'object'}->Load( $self->ObjectId() );
+ }
+ return ($self->{'object'});
+
+
+}
+
+
+
+=head2 GrantRight { Right => RIGHTNAME, Object => undef }
+
+A helper function which calls RT::ACE->Create
+
+
+
+ Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's
+ false.
+
+=cut
+
+sub GrantRight {
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Object => undef,
+ @_
+ );
+
+ return (0, "Permission denied") if $args{'Right'} eq 'ExecuteCode'
+ and RT->Config->Get('DisallowExecuteCode');
+
+ #ACL check handled in ACE.pm
+ my $ace = RT::ACE->new( $self->CurrentUser );
+
+ my $type = $self->_GetPrincipalTypeForACL();
+
+ RT->System->QueueCacheNeedsUpdate(1) if $args{'Right'} eq 'SeeQueue';
+
+ # If it's a user, we really want to grant the right to their
+ # user equivalence group
+ return $ace->Create(
+ RightName => $args{'Right'},
+ Object => $args{'Object'},
+ PrincipalType => $type,
+ PrincipalId => $self->Id,
+ );
+}
+
+
+=head2 RevokeRight { Right => "RightName", Object => "object" }
+
+Delete a right that a user has
+
+
+ Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's
+ false.
+
+
+=cut
+
+sub RevokeRight {
+
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Object => undef,
+ @_
+ );
+
+ #if we haven't specified any sort of right, we're talking about a global right
+ if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
+ $args{'Object'} = $RT::System;
+ }
+ #ACL check handled in ACE.pm
+ my $type = $self->_GetPrincipalTypeForACL();
+
+ my $ace = RT::ACE->new( $self->CurrentUser );
+ my ($status, $msg) = $ace->LoadByValues(
+ RightName => $args{'Right'},
+ Object => $args{'Object'},
+ PrincipalType => $type,
+ PrincipalId => $self->Id
+ );
+
+ if ( not $status and $msg =~ /Invalid right/ ) {
+ $RT::Logger->warn("Tried to revoke the invalid right '$args{Right}', ignoring it.");
+ return (1);
+ }
+
+ RT->System->QueueCacheNeedsUpdate(1) if $args{'Right'} eq 'SeeQueue';
+ return ($status, $msg) unless $status;
+ return $ace->Delete;
+}
+
+
+
+=head2 HasRight (Right => 'right' Object => undef)
+
+Checks to see whether this principal has the right "Right" for the Object
+specified. This takes the params:
+
+=over 4
+
+=item Right
+
+name of a right
+
+=item Object
+
+an RT style object (->id will get its id)
+
+=back
+
+Returns 1 if a matching ACE was found. Returns undef if no ACE was found.
+
+Use L</HasRights> to fill a fast cache, especially if you're going to
+check many different rights with the same principal and object.
+
+=cut
+
+sub HasRight {
+
+ my $self = shift;
+ my %args = ( Right => undef,
+ Object => undef,
+ EquivObjects => undef,
+ @_,
+ );
+
+ # RT's SystemUser always has all rights
+ if ( $self->id == RT->SystemUser->id ) {
+ return 1;
+ }
+
+ $args{'Right'} = RT::ACE->CanonicalizeRightName( $args{'Right'} );
+ unless ( $args{'Right'} ) {
+ $RT::Logger->error(
+ "Invalid right. Couldn't canonicalize right '$args{'Right'}'");
+ return undef;
+ }
+
+ return undef if $args{'Right'} eq 'ExecuteCode'
+ and RT->Config->Get('DisallowExecuteCode');
+
+ $args{'EquivObjects'} = [ @{ $args{'EquivObjects'} } ]
+ if $args{'EquivObjects'};
+
+ if ( $self->__Value('Disabled') ) {
+ $RT::Logger->debug( "Disabled User #"
+ . $self->id
+ . " failed access check for "
+ . $args{'Right'} );
+ return (undef);
+ }
+
+ if ( eval { $args{'Object'}->id } ) {
+ push @{ $args{'EquivObjects'} }, $args{'Object'};
+ } else {
+ $RT::Logger->crit("HasRight called with no valid object");
+ return (undef);
+ }
+
+ {
+ my $cached = $_ACL_CACHE->fetch(
+ $self->id .';:;'. ref($args{'Object'}) .'-'. $args{'Object'}->id
+ );
+ return $cached->{'SuperUser'} || $cached->{ $args{'Right'} }
+ if $cached;
+ }
+
+ unshift @{ $args{'EquivObjects'} },
+ $args{'Object'}->ACLEquivalenceObjects;
+
+ unshift @{ $args{'EquivObjects'} }, $RT::System
+ unless $self->can('_IsOverrideGlobalACL')
+ && $self->_IsOverrideGlobalACL( $args{'Object'} );
+
+ # If we've cached a win or loss for this lookup say so
+
+# Construct a hashkeys to cache decisions:
+# 1) full_hashkey - key for any result and for full combination of uid, right and objects
+# 2) short_hashkey - one key for each object to store positive results only, it applies
+# only to direct group rights and partly to role rights
+ my $full_hashkey = join (";:;", $self->id, $args{'Right'});
+ foreach ( @{ $args{'EquivObjects'} } ) {
+ my $ref_id = $self->_ReferenceId($_);
+ $full_hashkey .= ";:;".$ref_id;
+
+ my $short_hashkey = join(";:;", $self->id, $args{'Right'}, $ref_id);
+ my $cached_answer = $_ACL_CACHE->fetch($short_hashkey);
+ return $cached_answer > 0 if defined $cached_answer;
+ }
+
+ {
+ my $cached_answer = $_ACL_CACHE->fetch($full_hashkey);
+ return $cached_answer > 0 if defined $cached_answer;
+ }
+
+ my ( $hitcount, $via_obj ) = $self->_HasRight(%args);
+
+ $_ACL_CACHE->set( $full_hashkey => $hitcount ? 1 : -1 );
+ $_ACL_CACHE->set( join(';:;', $self->id, $args{'Right'},$via_obj) => 1 )
+ if $via_obj && $hitcount;
+
+ return ($hitcount);
+}
+
+=head2 HasRights
+
+Returns a hash reference with all rights this principal has on an
+object. Takes Object as a named argument.
+
+Main use case of this method is the following:
+
+ $ticket->CurrentUser->PrincipalObj->HasRights( Object => $ticket );
+ ...
+ $ticket->CurrentUserHasRight('A');
+ ...
+ $ticket->CurrentUserHasRight('Z');
+
+Results are cached and the cache is used in this and, as well, in L</HasRight>
+method speeding it up. Don't use hash reference returned by this method
+directly for rights checks as it's more complicated then it seems, especially
+considering config options like 'DisallowExecuteCode'.
+
+=cut
+
+sub HasRights {
+ my $self = shift;
+ my %args = (
+ Object => undef,
+ EquivObjects => undef,
+ @_
+ );
+ return {} if $self->__Value('Disabled');
+
+ my $object = $args{'Object'};
+ unless ( eval { $object->id } ) {
+ $RT::Logger->crit("HasRights called with no valid object");
+ }
+
+ my $cache_key = $self->id .';:;'. ref($object) .'-'. $object->id;
+ my $cached = $_ACL_CACHE->fetch($cache_key);
+ return $cached if $cached;
+
+ push @{ $args{'EquivObjects'} }, $object;
+ unshift @{ $args{'EquivObjects'} },
+ $args{'Object'}->ACLEquivalenceObjects;
+ unshift @{ $args{'EquivObjects'} }, $RT::System
+ unless $self->can('_IsOverrideGlobalACL')
+ && $self->_IsOverrideGlobalACL( $object );
+
+ my %res = ();
+ {
+ my $query
+ = "SELECT DISTINCT ACL.RightName "
+ . $self->_HasGroupRightQuery(
+ EquivObjects => $args{'EquivObjects'}
+ );
+ my $rights = $RT::Handle->dbh->selectcol_arrayref($query);
+ unless ($rights) {
+ $RT::Logger->warning( $RT::Handle->dbh->errstr );
+ return ();
+ }
+ $res{$_} = 1 foreach @$rights;
+ }
+ my $roles;
+ {
+ my $query
+ = "SELECT DISTINCT Groups.Type "
+ . $self->_HasRoleRightQuery(
+ EquivObjects => $args{'EquivObjects'}
+ );
+ $roles = $RT::Handle->dbh->selectcol_arrayref($query);
+ unless ($roles) {
+ $RT::Logger->warning( $RT::Handle->dbh->errstr );
+ return ();
+ }
+ }
+ if ( @$roles ) {
+ my $query
+ = "SELECT DISTINCT ACL.RightName "
+ . $self->_RolesWithRightQuery(
+ EquivObjects => $args{'EquivObjects'}
+ )
+ . ' AND ('. join( ' OR ', map "PrincipalType = '$_'", @$roles ) .')'
+ ;
+ my $rights = $RT::Handle->dbh->selectcol_arrayref($query);
+ unless ($rights) {
+ $RT::Logger->warning( $RT::Handle->dbh->errstr );
+ return ();
+ }
+ $res{$_} = 1 foreach @$rights;
+ }
+
+ delete $res{'ExecuteCode'} if
+ RT->Config->Get('DisallowExecuteCode');
+
+ $_ACL_CACHE->store( $cache_key, \%res );
+ return \%res;
+}
+
+=head2 _HasRight
+
+Low level HasRight implementation, use HasRight method instead.
+
+=cut
+
+sub _HasRight {
+ my $self = shift;
+ {
+ my ( $hit, @other ) = $self->_HasGroupRight(@_);
+ return ( $hit, @other ) if $hit;
+ }
+ {
+ my ( $hit, @other ) = $self->_HasRoleRight(@_);
+ return ( $hit, @other ) if $hit;
+ }
+ return (0);
+}