76582d67529eaf96b42306a9450ed55092613cb0
[freeside.git] / rt / lib / RT / Principal.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 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     if ( my $right = RT::ACE->CanonicalizeRightName( $args{'Right'} ) ) {
267         $args{'Right'} = $right;
268     } else {
269         $RT::Logger->error(
270                "Invalid right. Couldn't canonicalize right '$args{'Right'}'");
271         return undef;
272     }
273
274     return undef if $args{'Right'} eq 'ExecuteCode'
275         and RT->Config->Get('DisallowExecuteCode');
276
277     $args{'EquivObjects'} = [ @{ $args{'EquivObjects'} } ]
278         if $args{'EquivObjects'};
279
280     if ( $self->__Value('Disabled') ) {
281         $RT::Logger->debug(   "Disabled User #"
282                             . $self->id
283                             . " failed access check for "
284                             . $args{'Right'} );
285         return (undef);
286     }
287
288     if ( eval { $args{'Object'}->id } ) {
289         push @{ $args{'EquivObjects'} }, $args{'Object'};
290     } else {
291         $RT::Logger->crit("HasRight called with no valid object");
292         return (undef);
293     }
294
295     {
296         my $cached = $_ACL_CACHE->fetch(
297             $self->id .';:;'. ref($args{'Object'}) .'-'. $args{'Object'}->id
298         );
299         return $cached->{'SuperUser'} || $cached->{ $args{'Right'} }
300             if $cached;
301     }
302
303     unshift @{ $args{'EquivObjects'} },
304         $args{'Object'}->ACLEquivalenceObjects;
305
306     unshift @{ $args{'EquivObjects'} }, $RT::System
307         unless $self->can('_IsOverrideGlobalACL')
308             && $self->_IsOverrideGlobalACL( $args{'Object'} );
309
310     # If we've cached a win or loss for this lookup say so
311
312 # Construct a hashkeys to cache decisions:
313 # 1) full_hashkey - key for any result and for full combination of uid, right and objects
314 # 2) short_hashkey - one key for each object to store positive results only, it applies
315 # only to direct group rights and partly to role rights
316     my $full_hashkey = join (";:;", $self->id, $args{'Right'});
317     foreach ( @{ $args{'EquivObjects'} } ) {
318         my $ref_id = $self->_ReferenceId($_);
319         $full_hashkey .= ";:;".$ref_id;
320
321         my $short_hashkey = join(";:;", $self->id, $args{'Right'}, $ref_id);
322         my $cached_answer = $_ACL_CACHE->fetch($short_hashkey);
323         return $cached_answer > 0 if defined $cached_answer;
324     }
325
326     {
327         my $cached_answer = $_ACL_CACHE->fetch($full_hashkey);
328         return $cached_answer > 0 if defined $cached_answer;
329     }
330
331     my ( $hitcount, $via_obj ) = $self->_HasRight(%args);
332
333     $_ACL_CACHE->set( $full_hashkey => $hitcount ? 1 : -1 );
334     $_ACL_CACHE->set( join(';:;',  $self->id, $args{'Right'},$via_obj) => 1 )
335         if $via_obj && $hitcount;
336
337     return ($hitcount);
338 }
339
340 =head2 HasRights
341
342 Returns a hash reference with all rights this principal has on an
343 object. Takes Object as a named argument.
344
345 Main use case of this method is the following:
346
347     $ticket->CurrentUser->PrincipalObj->HasRights( Object => $ticket );
348     ...
349     $ticket->CurrentUserHasRight('A');
350     ...
351     $ticket->CurrentUserHasRight('Z');
352
353 Results are cached and the cache is used in this and, as well, in L</HasRight>
354 method speeding it up. Don't use hash reference returned by this method
355 directly for rights checks as it's more complicated then it seems, especially
356 considering config options like 'DisallowExecuteCode'.
357
358 =cut
359
360 sub HasRights {
361     my $self = shift;
362     my %args = (
363         Object       => undef,
364         EquivObjects => undef,
365         @_
366     );
367     return {} if $self->__Value('Disabled');
368
369     my $object = $args{'Object'};
370     unless ( eval { $object->id } ) {
371         $RT::Logger->crit("HasRights called with no valid object");
372     }
373
374     my $cache_key = $self->id .';:;'. ref($object) .'-'. $object->id;
375     my $cached = $_ACL_CACHE->fetch($cache_key);
376     return $cached if $cached;
377
378     push @{ $args{'EquivObjects'} }, $object;
379     unshift @{ $args{'EquivObjects'} },
380         $args{'Object'}->ACLEquivalenceObjects;
381     unshift @{ $args{'EquivObjects'} }, $RT::System
382         unless $self->can('_IsOverrideGlobalACL')
383             && $self->_IsOverrideGlobalACL( $object );
384
385     my %res = ();
386     {
387         my $query
388             = "SELECT DISTINCT ACL.RightName "
389             . $self->_HasGroupRightQuery(
390                 EquivObjects => $args{'EquivObjects'}
391             );
392         my $rights = $RT::Handle->dbh->selectcol_arrayref($query);
393         unless ($rights) {
394             $RT::Logger->warning( $RT::Handle->dbh->errstr );
395             return ();
396         }
397         $res{$_} = 1 foreach @$rights;
398     }
399     my $roles;
400     {
401         my $query
402             = "SELECT DISTINCT Groups.Type "
403             . $self->_HasRoleRightQuery(
404                 EquivObjects => $args{'EquivObjects'}
405             );
406         $roles = $RT::Handle->dbh->selectcol_arrayref($query);
407         unless ($roles) {
408             $RT::Logger->warning( $RT::Handle->dbh->errstr );
409             return ();
410         }
411     }
412     if ( @$roles ) {
413         my $query
414             = "SELECT DISTINCT ACL.RightName "
415             . $self->_RolesWithRightQuery(
416                 EquivObjects => $args{'EquivObjects'}
417             )
418             . ' AND ('. join( ' OR ', map "PrincipalType = '$_'", @$roles ) .')'
419         ;
420         my $rights = $RT::Handle->dbh->selectcol_arrayref($query);
421         unless ($rights) {
422             $RT::Logger->warning( $RT::Handle->dbh->errstr );
423             return ();
424         }
425         $res{$_} = 1 foreach @$rights;
426     }
427
428     delete $res{'ExecuteCode'} if 
429         RT->Config->Get('DisallowExecuteCode');
430
431     $_ACL_CACHE->store( $cache_key, \%res );
432     return \%res;
433 }
434
435 =head2 _HasRight
436
437 Low level HasRight implementation, use HasRight method instead.
438
439 =cut
440
441 sub _HasRight {
442     my $self = shift;
443     {
444         my ( $hit, @other ) = $self->_HasGroupRight(@_);
445         return ( $hit, @other ) if $hit;
446     }
447     {
448         my ( $hit, @other ) = $self->_HasRoleRight(@_);
449         return ( $hit, @other ) if $hit;
450     }
451     return (0);
452 }
453
454 # this method handles role rights partly in situations
455 # where user plays role X on an object and as well the right is
456 # assigned to this role X of the object, for example right CommentOnTicket
457 # is granted to Cc role of a queue and user is in cc list of the queue
458 sub _HasGroupRight {
459     my $self = shift;
460     my %args = ( Right        => undef,
461                  EquivObjects => [],
462                  @_
463                );
464
465     my $query
466         = "SELECT ACL.id, ACL.ObjectType, ACL.ObjectId "
467         . $self->_HasGroupRightQuery( %args );
468
469     $self->_Handle->ApplyLimits( \$query, 1 );
470     my ( $hit, $obj, $id ) = $self->_Handle->FetchResult($query);
471     return (0) unless $hit;
472
473     $obj .= "-$id" if $id;
474     return ( 1, $obj );
475 }
476
477 sub _HasGroupRightQuery {
478     my $self = shift;
479     my %args = (
480         Right        => undef,
481         EquivObjects => [],
482         @_
483     );
484
485     my $query
486         = "FROM ACL, Principals, CachedGroupMembers WHERE "
487
488         # Never find disabled groups.
489         . "Principals.id = ACL.PrincipalId "
490         . "AND Principals.PrincipalType = 'Group' "
491         . "AND Principals.Disabled = 0 "
492
493 # See if the principal is a member of the group recursively or _is the rightholder_
494 # never find recursively disabled group members
495 # also, check to see if the right is being granted _directly_ to this principal,
496 #  as is the case when we want to look up group rights
497         . "AND CachedGroupMembers.GroupId  = ACL.PrincipalId "
498         . "AND CachedGroupMembers.GroupId  = Principals.id "
499         . "AND CachedGroupMembers.MemberId = ". $self->Id . " "
500         . "AND CachedGroupMembers.Disabled = 0 ";
501
502     my @clauses;
503     foreach my $obj ( @{ $args{'EquivObjects'} } ) {
504         my $type = ref($obj) || $obj;
505         my $clause = "ACL.ObjectType = '$type'";
506
507         if ( defined eval { $obj->id } ) {    # it might be 0
508             $clause .= " AND ACL.ObjectId = " . $obj->id;
509         }
510
511         push @clauses, "($clause)";
512     }
513     if (@clauses) {
514         $query .= " AND (" . join( ' OR ', @clauses ) . ")";
515     }
516     if ( my $right = $args{'Right'} ) {
517         # Only find superuser or rights with the name $right
518         $query .= " AND (ACL.RightName = 'SuperUser' "
519             . ( $right ne 'SuperUser' ? "OR ACL.RightName = '$right'" : '' )
520         . ") ";
521     }
522     return $query;
523 }
524
525 sub _HasRoleRight {
526     my $self = shift;
527     my %args = ( Right        => undef,
528                  EquivObjects => [],
529                  @_
530                );
531
532     my @roles = $self->RolesWithRight(%args);
533     return 0 unless @roles;
534
535     my $query = "SELECT Groups.id "
536         . $self->_HasRoleRightQuery( %args, Roles => \@roles );
537
538     $self->_Handle->ApplyLimits( \$query, 1 );
539     my ($hit) = $self->_Handle->FetchResult($query);
540     return (1) if $hit;
541
542     return 0;
543 }
544
545 sub _HasRoleRightQuery {
546     my $self = shift;
547     my %args = ( Right        => undef,
548                  EquivObjects => [],
549                  Roles        => undef,
550                  @_
551                );
552
553     my $query =
554         " FROM Groups, Principals, CachedGroupMembers WHERE "
555
556         # Never find disabled things
557         . "Principals.Disabled = 0 " . "AND CachedGroupMembers.Disabled = 0 "
558
559         # We always grant rights to Groups
560         . "AND Principals.id = Groups.id "
561         . "AND Principals.PrincipalType = 'Group' "
562
563 # See if the principal is a member of the group recursively or _is the rightholder_
564 # never find recursively disabled group members
565 # also, check to see if the right is being granted _directly_ to this principal,
566 #  as is the case when we want to look up group rights
567         . "AND Principals.id = CachedGroupMembers.GroupId "
568         . "AND CachedGroupMembers.MemberId = " . $self->Id . " "
569     ;
570
571     if ( $args{'Roles'} ) {
572         $query .= "AND (" . join( ' OR ', map "Groups.Type = '$_'", @{ $args{'Roles'} } ) . ")";
573     }
574
575     my (@object_clauses);
576     foreach my $obj ( @{ $args{'EquivObjects'} } ) {
577         my $type = ref($obj) ? ref($obj) : $obj;
578
579         my $clause = "Groups.Domain = '$type-Role'";
580
581         # XXX: Groups.Instance is VARCHAR in DB, we should quote value
582         # if we want mysql 4.0 use indexes here. we MUST convert that
583         # field to integer and drop this quotes.
584         if ( my $id = eval { $obj->id } ) {
585             $clause .= " AND Groups.Instance = '$id'";
586         }
587         push @object_clauses, "($clause)";
588     }
589     $query .= " AND (" . join( ' OR ', @object_clauses ) . ")";
590     return $query;
591 }
592
593 =head2 RolesWithRight
594
595 Returns list with names of roles that have right on
596 set of objects. Takes Right, EquiveObjects,
597 IncludeSystemRights and IncludeSuperusers arguments.
598
599 IncludeSystemRights is true by default, rights
600 granted systemwide are ignored when IncludeSystemRights
601 is set to a false value.
602
603 IncludeSuperusers is true by default, SuperUser right
604 is not checked if it's set to a false value.
605
606 =cut
607
608 sub RolesWithRight {
609     my $self = shift;
610     my %args = ( Right               => undef,
611                  IncludeSystemRights => 1,
612                  IncludeSuperusers   => 1,
613                  EquivObjects        => [],
614                  @_
615                );
616
617     return () if $args{'Right'} eq 'ExecuteCode'
618         and RT->Config->Get('DisallowExecuteCode');
619
620     my $query = "SELECT DISTINCT PrincipalType "
621         . $self->_RolesWithRightQuery( %args );
622
623     my $roles = $RT::Handle->dbh->selectcol_arrayref($query);
624     unless ($roles) {
625         $RT::Logger->warning( $RT::Handle->dbh->errstr );
626         return ();
627     }
628     return @$roles;
629 }
630
631 sub _RolesWithRightQuery {
632     my $self = shift;
633     my %args = ( Right               => undef,
634                  IncludeSystemRights => 1,
635                  IncludeSuperusers   => 1,
636                  EquivObjects        => [],
637                  @_
638                );
639
640     my $query = " FROM ACL WHERE"
641
642         # we need only roles
643         . " PrincipalType != 'Group'";
644
645     if ( my $right = $args{'Right'} ) {
646         $query .=
647             # Only find superuser or rights with the requested right
648             " AND ( RightName = '$right' "
649
650             # Check SuperUser if we were asked to
651             . ( $args{'IncludeSuperusers'} ? "OR RightName = 'SuperUser' " : '' )
652             . ")";
653     }
654
655     # skip rights granted on system level if we were asked to
656     unless ( $args{'IncludeSystemRights'} ) {
657         $query .= " AND ObjectType != 'RT::System'";
658     }
659
660     my (@object_clauses);
661     foreach my $obj ( @{ $args{'EquivObjects'} } ) {
662         my $type = ref($obj) ? ref($obj) : $obj;
663
664         my $object_clause = "ObjectType = '$type'";
665         if ( my $id = eval { $obj->id } ) {
666             $object_clause .= " AND ObjectId = $id";
667         }
668         push @object_clauses, "($object_clause)";
669     }
670
671     # find ACLs that are related to our objects only
672     $query .= " AND (" . join( ' OR ', @object_clauses ) . ")"
673         if @object_clauses;
674
675     return $query;
676 }
677
678
679 =head2 InvalidateACLCache
680
681 Cleans out and reinitializes the user rights cache
682
683 =cut
684
685 sub InvalidateACLCache {
686     $_ACL_CACHE = Cache::Simple::TimedExpiry->new();
687     my $lifetime;
688     $lifetime = $RT::Config->Get('ACLCacheLifetime') if $RT::Config;
689     $_ACL_CACHE->expire_after( $lifetime || 60 );
690 }
691
692
693
694
695
696 =head2 _GetPrincipalTypeForACL
697
698 Gets the principal type. if it's a user, it's a user. if it's a role group and it has a Type, 
699 return that. if it has no type, return group.
700
701 =cut
702
703 sub _GetPrincipalTypeForACL {
704     my $self = shift;
705     if ($self->PrincipalType eq 'Group' && $self->Object->Domain =~ /Role$/) {
706         return $self->Object->Type;
707     } else {
708         return $self->PrincipalType;
709     }
710 }
711
712
713
714 =head2 _ReferenceId
715
716 Returns a list uniquely representing an object or normal scalar.
717
718 For a scalar, its string value is returned.
719 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 "-".
720 For an object that has an id() method which returns false, its class name is returned.
721
722 =cut
723
724 sub _ReferenceId {
725     my $self = shift;
726     my $scalar = shift;
727     my $id = eval { $scalar->id };
728     if ($@) {
729         return $scalar;
730     } elsif ($id) {
731         return ref($scalar) . "-" . $id;
732     } else {
733         return ref($scalar);
734     }
735 }
736
737
738
739
740
741
742 =head2 id
743
744 Returns the current value of id.
745 (In the database, id is stored as int(11).)
746
747
748 =cut
749
750
751 =head2 PrincipalType
752
753 Returns the current value of PrincipalType.
754 (In the database, PrincipalType is stored as varchar(16).)
755
756
757
758 =head2 SetPrincipalType VALUE
759
760
761 Set PrincipalType to VALUE.
762 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
763 (In the database, PrincipalType will be stored as a varchar(16).)
764
765
766 =cut
767
768
769 =head2 ObjectId
770
771 Returns the current value of ObjectId.
772 (In the database, ObjectId is stored as int(11).)
773
774
775
776 =head2 SetObjectId VALUE
777
778
779 Set ObjectId to VALUE.
780 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
781 (In the database, ObjectId will be stored as a int(11).)
782
783
784 =cut
785
786
787 =head2 Disabled
788
789 Returns the current value of Disabled.
790 (In the database, Disabled is stored as smallint(6).)
791
792
793
794 =head2 SetDisabled VALUE
795
796
797 Set Disabled to VALUE.
798 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
799 (In the database, Disabled will be stored as a smallint(6).)
800
801
802 =cut
803
804
805
806 sub _CoreAccessible {
807     {
808
809         id =>
810                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
811         PrincipalType =>
812                 {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
813         ObjectId =>
814                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
815         Disabled =>
816                 {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
817
818  }
819 };
820
821 RT::Base->_ImportOverlays();
822
823 1;