175f1b003dab5ee5b0ffb4d13e1a893bd715d228
[freeside.git] / rt / lib / RT / Principal.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17 #
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37 #
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 #
50
51 package RT::Principal;
52
53 use strict;
54 use warnings;
55
56
57 use base 'RT::Record';
58
59 sub Table {'Principals'}
60
61
62
63 use Cache::Simple::TimedExpiry;
64
65
66 use RT;
67 use RT::Group;
68 use RT::User;
69
70 # Set up the ACL cache on startup
71 our $_ACL_CACHE;
72 InvalidateACLCache();
73
74
75 =head2 IsGroup
76
77 Returns true if this principal is a group. 
78 Returns undef, otherwise
79
80 =cut
81
82 sub IsGroup {
83     my $self = shift;
84     if ( defined $self->PrincipalType && 
85             $self->PrincipalType eq 'Group' ) {
86         return 1;
87     }
88     return undef;
89 }
90
91
92
93 =head2 IsUser 
94
95 Returns true if this principal is a User. 
96 Returns undef, otherwise
97
98 =cut
99
100 sub IsUser {
101     my $self = shift;
102     if ($self->PrincipalType eq 'User') {
103         return(1);
104     }
105     else {
106         return undef;
107     }
108 }
109
110
111
112 =head2 Object
113
114 Returns the user or group associated with this principal
115
116 =cut
117
118 sub Object {
119     my $self = shift;
120
121     unless ( $self->{'object'} ) {
122         if ( $self->IsUser ) {
123            $self->{'object'} = RT::User->new($self->CurrentUser);
124         }
125         elsif ( $self->IsGroup ) {
126             $self->{'object'}  = RT::Group->new($self->CurrentUser);
127         }
128         else { 
129             $RT::Logger->crit("Found a principal (".$self->Id.") that was neither a user nor a group");
130             return(undef);
131         }
132         $self->{'object'}->Load( $self->ObjectId() );
133     }
134     return ($self->{'object'});
135
136
137 }
138
139
140
141 =head2 GrantRight  { Right => RIGHTNAME, Object => undef }
142
143 A helper function which calls RT::ACE->Create
144
145
146
147    Returns a tuple of (STATUS, MESSAGE);  If the call succeeded, STATUS is true. Otherwise it's 
148    false.
149
150 =cut
151
152 sub GrantRight {
153     my $self = shift;
154     my %args = (
155         Right => undef,
156         Object => undef,
157         @_
158     );
159
160     return (0, "Permission denied") if $args{'Right'} eq 'ExecuteCode'
161         and RT->Config->Get('DisallowExecuteCode');
162
163     #ACL check handled in ACE.pm
164     my $ace = RT::ACE->new( $self->CurrentUser );
165
166     my $type = $self->_GetPrincipalTypeForACL();
167
168     RT->System->QueueCacheNeedsUpdate(1) if $args{'Right'} eq 'SeeQueue';
169
170     # If it's a user, we really want to grant the right to their 
171     # user equivalence group
172     return $ace->Create(
173         RightName     => $args{'Right'},
174         Object        => $args{'Object'},
175         PrincipalType => $type,
176         PrincipalId   => $self->Id,
177     );
178 }
179
180
181 =head2 RevokeRight { Right => "RightName", Object => "object" }
182
183 Delete a right that a user has 
184
185
186    Returns a tuple of (STATUS, MESSAGE);  If the call succeeded, STATUS is true. Otherwise it's 
187       false.
188
189
190 =cut
191
192 sub RevokeRight {
193
194     my $self = shift;
195     my %args = (
196         Right  => undef,
197         Object => undef,
198         @_
199     );
200
201     #if we haven't specified any sort of right, we're talking about a global right
202     if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
203         $args{'Object'} = $RT::System;
204     }
205     #ACL check handled in ACE.pm
206     my $type = $self->_GetPrincipalTypeForACL();
207
208     my $ace = RT::ACE->new( $self->CurrentUser );
209     my ($status, $msg) = $ace->LoadByValues(
210         RightName     => $args{'Right'},
211         Object        => $args{'Object'},
212         PrincipalType => $type,
213         PrincipalId   => $self->Id
214     );
215
216     if ( not $status and $msg =~ /Invalid right/ ) {
217         $RT::Logger->warn("Tried to revoke the invalid right '$args{Right}', ignoring it.");
218         return (1);
219     }
220
221     RT->System->QueueCacheNeedsUpdate(1) if $args{'Right'} eq 'SeeQueue';
222     return ($status, $msg) unless $status;
223     return $ace->Delete;
224 }
225
226
227
228 =head2 HasRight (Right => 'right' Object => undef)
229
230 Checks to see whether this principal has the right "Right" for the Object
231 specified. This takes the params:
232
233 =over 4
234
235 =item Right
236
237 name of a right
238
239 =item Object
240
241 an RT style object (->id will get its id)
242
243 =back
244
245 Returns 1 if a matching ACE was found. Returns undef if no ACE was found.
246
247 Use L</HasRights> to fill a fast cache, especially if you're going to
248 check many different rights with the same principal and object.
249
250 =cut
251
252 sub HasRight {
253
254     my $self = shift;
255     my %args = ( Right        => undef,
256                  Object       => undef,
257                  EquivObjects => undef,
258                  @_,
259                );
260
261     # RT's SystemUser always has all rights
262     if ( $self->id == RT->SystemUser->id ) {
263         return 1;
264     }
265
266     $args{'Right'} = RT::ACE->CanonicalizeRightName( $args{'Right'} );
267     unless ( $args{'Right'} ) {
268         $RT::Logger->error(
269                "Invalid right. Couldn't canonicalize right '$args{'Right'}'");
270         return undef;
271     }
272
273     return undef if $args{'Right'} eq 'ExecuteCode'
274         and RT->Config->Get('DisallowExecuteCode');
275
276     $args{'EquivObjects'} = [ @{ $args{'EquivObjects'} } ]
277         if $args{'EquivObjects'};
278
279     if ( $self->__Value('Disabled') ) {
280         $RT::Logger->debug(   "Disabled User #"
281                             . $self->id
282                             . " failed access check for "
283                             . $args{'Right'} );
284         return (undef);
285     }
286
287     if ( eval { $args{'Object'}->id } ) {
288         push @{ $args{'EquivObjects'} }, $args{'Object'};
289     } else {
290         $RT::Logger->crit("HasRight called with no valid object");
291         return (undef);
292     }
293
294     {
295         my $cached = $_ACL_CACHE->fetch(
296             $self->id .';:;'. ref($args{'Object'}) .'-'. $args{'Object'}->id
297         );
298         return $cached->{'SuperUser'} || $cached->{ $args{'Right'} }
299             if $cached;
300     }
301
302     unshift @{ $args{'EquivObjects'} },
303         $args{'Object'}->ACLEquivalenceObjects;
304
305     unshift @{ $args{'EquivObjects'} }, $RT::System
306         unless $self->can('_IsOverrideGlobalACL')
307             && $self->_IsOverrideGlobalACL( $args{'Object'} );
308
309     # If we've cached a win or loss for this lookup say so
310
311 # Construct a hashkeys to cache decisions:
312 # 1) full_hashkey - key for any result and for full combination of uid, right and objects
313 # 2) short_hashkey - one key for each object to store positive results only, it applies
314 # only to direct group rights and partly to role rights
315     my $full_hashkey = join (";:;", $self->id, $args{'Right'});
316     foreach ( @{ $args{'EquivObjects'} } ) {
317         my $ref_id = $self->_ReferenceId($_);
318         $full_hashkey .= ";:;".$ref_id;
319
320         my $short_hashkey = join(";:;", $self->id, $args{'Right'}, $ref_id);
321         my $cached_answer = $_ACL_CACHE->fetch($short_hashkey);
322         return $cached_answer > 0 if defined $cached_answer;
323     }
324
325     {
326         my $cached_answer = $_ACL_CACHE->fetch($full_hashkey);
327         return $cached_answer > 0 if defined $cached_answer;
328     }
329
330     my ( $hitcount, $via_obj ) = $self->_HasRight(%args);
331
332     $_ACL_CACHE->set( $full_hashkey => $hitcount ? 1 : -1 );
333     $_ACL_CACHE->set( join(';:;',  $self->id, $args{'Right'},$via_obj) => 1 )
334         if $via_obj && $hitcount;
335
336     return ($hitcount);
337 }
338
339 =head2 HasRights
340
341 Returns a hash reference with all rights this principal has on an
342 object. Takes Object as a named argument.
343
344 Main use case of this method is the following:
345
346     $ticket->CurrentUser->PrincipalObj->HasRights( Object => $ticket );
347     ...
348     $ticket->CurrentUserHasRight('A');
349     ...
350     $ticket->CurrentUserHasRight('Z');
351
352 Results are cached and the cache is used in this and, as well, in L</HasRight>
353 method speeding it up. Don't use hash reference returned by this method
354 directly for rights checks as it's more complicated then it seems, especially
355 considering config options like 'DisallowExecuteCode'.
356
357 =cut
358
359 sub HasRights {
360     my $self = shift;
361     my %args = (
362         Object       => undef,
363         EquivObjects => undef,
364         @_
365     );
366     return {} if $self->__Value('Disabled');
367
368     my $object = $args{'Object'};
369     unless ( eval { $object->id } ) {
370         $RT::Logger->crit("HasRights called with no valid object");
371     }
372
373     my $cache_key = $self->id .';:;'. ref($object) .'-'. $object->id;
374     my $cached = $_ACL_CACHE->fetch($cache_key);
375     return $cached if $cached;
376
377     push @{ $args{'EquivObjects'} }, $object;
378     unshift @{ $args{'EquivObjects'} },
379         $args{'Object'}->ACLEquivalenceObjects;
380     unshift @{ $args{'EquivObjects'} }, $RT::System
381         unless $self->can('_IsOverrideGlobalACL')
382             && $self->_IsOverrideGlobalACL( $object );
383
384     my %res = ();
385     {
386         my $query
387             = "SELECT DISTINCT ACL.RightName "
388             . $self->_HasGroupRightQuery(
389                 EquivObjects => $args{'EquivObjects'}
390             );
391         my $rights = $RT::Handle->dbh->selectcol_arrayref($query);
392         unless ($rights) {
393             $RT::Logger->warning( $RT::Handle->dbh->errstr );
394             return ();
395         }
396         $res{$_} = 1 foreach @$rights;
397     }
398     my $roles;
399     {
400         my $query
401             = "SELECT DISTINCT Groups.Type "
402             . $self->_HasRoleRightQuery(
403                 EquivObjects => $args{'EquivObjects'}
404             );
405         $roles = $RT::Handle->dbh->selectcol_arrayref($query);
406         unless ($roles) {
407             $RT::Logger->warning( $RT::Handle->dbh->errstr );
408             return ();
409         }
410     }
411     if ( @$roles ) {
412         my $query
413             = "SELECT DISTINCT ACL.RightName "
414             . $self->_RolesWithRightQuery(
415                 EquivObjects => $args{'EquivObjects'}
416             )
417             . ' AND ('. join( ' OR ', map "PrincipalType = '$_'", @$roles ) .')'
418         ;
419         my $rights = $RT::Handle->dbh->selectcol_arrayref($query);
420         unless ($rights) {
421             $RT::Logger->warning( $RT::Handle->dbh->errstr );
422             return ();
423         }
424         $res{$_} = 1 foreach @$rights;
425     }
426
427     delete $res{'ExecuteCode'} if 
428         RT->Config->Get('DisallowExecuteCode');
429
430     $_ACL_CACHE->store( $cache_key, \%res );
431     return \%res;
432 }
433
434 =head2 _HasRight
435
436 Low level HasRight implementation, use HasRight method instead.
437
438 =cut
439
440 sub _HasRight {
441     my $self = shift;
442     {
443         my ( $hit, @other ) = $self->_HasGroupRight(@_);
444         return ( $hit, @other ) if $hit;
445     }
446     {
447         my ( $hit, @other ) = $self->_HasRoleRight(@_);
448         return ( $hit, @other ) if $hit;
449     }
450     return (0);
451 }
452
453 # this method handles role rights partly in situations
454 # where user plays role X on an object and as well the right is
455 # assigned to this role X of the object, for example right CommentOnTicket
456 # is granted to Cc role of a queue and user is in cc list of the queue
457 sub _HasGroupRight {
458     my $self = shift;
459     my %args = ( Right        => undef,
460                  EquivObjects => [],
461                  @_
462                );
463
464     my $query
465         = "SELECT ACL.id, ACL.ObjectType, ACL.ObjectId "
466         . $self->_HasGroupRightQuery( %args );
467
468     $self->_Handle->ApplyLimits( \$query, 1 );
469     my ( $hit, $obj, $id ) = $self->_Handle->FetchResult($query);
470     return (0) unless $hit;
471
472     $obj .= "-$id" if $id;
473     return ( 1, $obj );
474 }
475
476 sub _HasGroupRightQuery {
477     my $self = shift;
478     my %args = (
479         Right        => undef,
480         EquivObjects => [],
481         @_
482     );
483
484     my $query
485         = "FROM ACL, Principals, CachedGroupMembers WHERE "
486
487         # Never find disabled groups.
488         . "Principals.id = ACL.PrincipalId "
489         . "AND Principals.PrincipalType = 'Group' "
490         . "AND Principals.Disabled = 0 "
491
492 # See if the principal is a member of the group recursively or _is the rightholder_
493 # never find recursively disabled group members
494 # also, check to see if the right is being granted _directly_ to this principal,
495 #  as is the case when we want to look up group rights
496         . "AND CachedGroupMembers.GroupId  = ACL.PrincipalId "
497         . "AND CachedGroupMembers.GroupId  = Principals.id "
498         . "AND CachedGroupMembers.MemberId = ". $self->Id . " "
499         . "AND CachedGroupMembers.Disabled = 0 ";
500
501     my @clauses;
502     foreach my $obj ( @{ $args{'EquivObjects'} } ) {
503         my $type = ref($obj) || $obj;
504         my $clause = "ACL.ObjectType = '$type'";
505
506         if ( defined eval { $obj->id } ) {    # it might be 0
507             $clause .= " AND ACL.ObjectId = " . $obj->id;
508         }
509
510         push @clauses, "($clause)";
511     }
512     if (@clauses) {
513         $query .= " AND (" . join( ' OR ', @clauses ) . ")";
514     }
515     if ( my $right = $args{'Right'} ) {
516         # Only find superuser or rights with the name $right
517         $query .= " AND (ACL.RightName = 'SuperUser' "
518             . ( $right ne 'SuperUser' ? "OR ACL.RightName = '$right'" : '' )
519         . ") ";
520     }
521     return $query;
522 }
523
524 sub _HasRoleRight {
525     my $self = shift;
526     my %args = ( Right        => undef,
527                  EquivObjects => [],
528                  @_
529                );
530
531     my @roles = $self->RolesWithRight(%args);
532     return 0 unless @roles;
533
534     my $query = "SELECT Groups.id "
535         . $self->_HasRoleRightQuery( %args, Roles => \@roles );
536
537     $self->_Handle->ApplyLimits( \$query, 1 );
538     my ($hit) = $self->_Handle->FetchResult($query);
539     return (1) if $hit;
540
541     return 0;
542 }
543
544 sub _HasRoleRightQuery {
545     my $self = shift;
546     my %args = ( Right        => undef,
547                  EquivObjects => [],
548                  Roles        => undef,
549                  @_
550                );
551
552     my $query =
553         " FROM Groups, Principals, CachedGroupMembers WHERE "
554
555         # Never find disabled things
556         . "Principals.Disabled = 0 " . "AND CachedGroupMembers.Disabled = 0 "
557
558         # We always grant rights to Groups
559         . "AND Principals.id = Groups.id "
560         . "AND Principals.PrincipalType = 'Group' "
561
562 # See if the principal is a member of the group recursively or _is the rightholder_
563 # never find recursively disabled group members
564 # also, check to see if the right is being granted _directly_ to this principal,
565 #  as is the case when we want to look up group rights
566         . "AND Principals.id = CachedGroupMembers.GroupId "
567         . "AND CachedGroupMembers.MemberId = " . $self->Id . " "
568     ;
569
570     if ( $args{'Roles'} ) {
571         $query .= "AND (" . join( ' OR ', map "Groups.Type = '$_'", @{ $args{'Roles'} } ) . ")";
572     }
573
574     my (@object_clauses);
575     foreach my $obj ( @{ $args{'EquivObjects'} } ) {
576         my $type = ref($obj) ? ref($obj) : $obj;
577
578         my $clause = "Groups.Domain = '$type-Role'";
579
580         # XXX: Groups.Instance is VARCHAR in DB, we should quote value
581         # if we want mysql 4.0 use indexes here. we MUST convert that
582         # field to integer and drop this quotes.
583         if ( my $id = eval { $obj->id } ) {
584             $clause .= " AND Groups.Instance = '$id'";
585         }
586         push @object_clauses, "($clause)";
587     }
588     $query .= " AND (" . join( ' OR ', @object_clauses ) . ")";
589     return $query;
590 }
591
592 =head2 RolesWithRight
593
594 Returns list with names of roles that have right on
595 set of objects. Takes Right, EquiveObjects,
596 IncludeSystemRights and IncludeSuperusers arguments.
597
598 IncludeSystemRights is true by default, rights
599 granted systemwide are ignored when IncludeSystemRights
600 is set to a false value.
601
602 IncludeSuperusers is true by default, SuperUser right
603 is not checked if it's set to a false value.
604
605 =cut
606
607 sub RolesWithRight {
608     my $self = shift;
609     my %args = ( Right               => undef,
610                  IncludeSystemRights => 1,
611                  IncludeSuperusers   => 1,
612                  EquivObjects        => [],
613                  @_
614                );
615
616     return () if $args{'Right'} eq 'ExecuteCode'
617         and RT->Config->Get('DisallowExecuteCode');
618
619     my $query = "SELECT DISTINCT PrincipalType "
620         . $self->_RolesWithRightQuery( %args );
621
622     my $roles = $RT::Handle->dbh->selectcol_arrayref($query);
623     unless ($roles) {
624         $RT::Logger->warning( $RT::Handle->dbh->errstr );
625         return ();
626     }
627     return @$roles;
628 }
629
630 sub _RolesWithRightQuery {
631     my $self = shift;
632     my %args = ( Right               => undef,
633                  IncludeSystemRights => 1,
634                  IncludeSuperusers   => 1,
635                  EquivObjects        => [],
636                  @_
637                );
638
639     my $query = " FROM ACL WHERE"
640
641         # we need only roles
642         . " PrincipalType != 'Group'";
643
644     if ( my $right = $args{'Right'} ) {
645         $query .=
646             # Only find superuser or rights with the requested right
647             " AND ( RightName = '$right' "
648
649             # Check SuperUser if we were asked to
650             . ( $args{'IncludeSuperusers'} ? "OR RightName = 'SuperUser' " : '' )
651             . ")";
652     }
653
654     # skip rights granted on system level if we were asked to
655     unless ( $args{'IncludeSystemRights'} ) {
656         $query .= " AND ObjectType != 'RT::System'";
657     }
658
659     my (@object_clauses);
660     foreach my $obj ( @{ $args{'EquivObjects'} } ) {
661         my $type = ref($obj) ? ref($obj) : $obj;
662
663         my $object_clause = "ObjectType = '$type'";
664         if ( my $id = eval { $obj->id } ) {
665             $object_clause .= " AND ObjectId = $id";
666         }
667         push @object_clauses, "($object_clause)";
668     }
669
670     # find ACLs that are related to our objects only
671     $query .= " AND (" . join( ' OR ', @object_clauses ) . ")"
672         if @object_clauses;
673
674     return $query;
675 }
676
677
678 =head2 InvalidateACLCache
679
680 Cleans out and reinitializes the user rights cache
681
682 =cut
683
684 sub InvalidateACLCache {
685     $_ACL_CACHE = Cache::Simple::TimedExpiry->new();
686     my $lifetime;
687     $lifetime = $RT::Config->Get('ACLCacheLifetime') if $RT::Config;
688     $_ACL_CACHE->expire_after( $lifetime || 60 );
689 }
690
691
692
693
694
695 =head2 _GetPrincipalTypeForACL
696
697 Gets the principal type. if it's a user, it's a user. if it's a role group and it has a Type, 
698 return that. if it has no type, return group.
699
700 =cut
701
702 sub _GetPrincipalTypeForACL {
703     my $self = shift;
704     if ($self->PrincipalType eq 'Group' && $self->Object->Domain =~ /Role$/) {
705         return $self->Object->Type;
706     } else {
707         return $self->PrincipalType;
708     }
709 }
710
711
712
713 =head2 _ReferenceId
714
715 Returns a list uniquely representing an object or normal scalar.
716
717 For a scalar, its string value is returned.
718 For an object that has an id() method which returns a value, its class name and id are returned as a string separated by a "-".
719 For an object that has an id() method which returns false, its class name is returned.
720
721 =cut
722
723 sub _ReferenceId {
724     my $self = shift;
725     my $scalar = shift;
726     my $id = eval { $scalar->id };
727     if ($@) {
728         return $scalar;
729     } elsif ($id) {
730         return ref($scalar) . "-" . $id;
731     } else {
732         return ref($scalar);
733     }
734 }
735
736
737
738
739
740
741 =head2 id
742
743 Returns the current value of id.
744 (In the database, id is stored as int(11).)
745
746
747 =cut
748
749
750 =head2 PrincipalType
751
752 Returns the current value of PrincipalType.
753 (In the database, PrincipalType is stored as varchar(16).)
754
755
756
757 =head2 SetPrincipalType VALUE
758
759
760 Set PrincipalType to VALUE.
761 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
762 (In the database, PrincipalType will be stored as a varchar(16).)
763
764
765 =cut
766
767
768 =head2 ObjectId
769
770 Returns the current value of ObjectId.
771 (In the database, ObjectId is stored as int(11).)
772
773
774
775 =head2 SetObjectId VALUE
776
777
778 Set ObjectId to VALUE.
779 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
780 (In the database, ObjectId will be stored as a int(11).)
781
782
783 =cut
784
785
786 =head2 Disabled
787
788 Returns the current value of Disabled.
789 (In the database, Disabled is stored as smallint(6).)
790
791
792
793 =head2 SetDisabled VALUE
794
795
796 Set Disabled to VALUE.
797 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
798 (In the database, Disabled will be stored as a smallint(6).)
799
800
801 =cut
802
803
804
805 sub _CoreAccessible {
806     {
807
808         id =>
809                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
810         PrincipalType =>
811                 {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
812         ObjectId =>
813                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
814         Disabled =>
815                 {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
816
817  }
818 };
819
820 RT::Base->_ImportOverlays();
821
822 1;