+ # grant SuperUser right to system user
+ {
+ my $test_ace = RT::ACE->new( RT->SystemUser );
+ $test_ace->LoadByCols(
+ PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => 1,
+ );
+ if ( $test_ace->id ) {
+ push @warns, "System user has global SuperUser right.";
+ } else {
+ my $ace = RT::ACE->new( RT->SystemUser );
+ my ( $val, $msg ) = $ace->_BootstrapCreate(
+ PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => 1,
+ );
+ return ($val, $msg) unless $val;
+ }
+ DBIx::SearchBuilder::Record::Cachable->FlushCache;
+ }
+
+ # system groups
+ # $self->loc('Everyone'); # For the string extractor to get a string to localize
+ # $self->loc('Privileged'); # For the string extractor to get a string to localize
+ # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
+ foreach my $name (qw(Everyone Privileged Unprivileged)) {
+ my $group = RT::Group->new( RT->SystemUser );
+ $group->LoadSystemInternalGroup( $name );
+ if ( $group->id ) {
+ push @warns, "System group '$name' already exists.";
+ next;
+ }
+
+ $group = RT::Group->new( RT->SystemUser );
+ my ( $val, $msg ) = $group->_Create(
+ Domain => 'SystemInternal',
+ Description => 'Pseudogroup for internal use', # loc
+ Name => $name,
+ Instance => '',
+ );
+ return ($val, $msg) unless $val;
+ }
+
+ # nobody
+ {
+ my $user = RT::User->new( RT->SystemUser );
+ $user->Load('Nobody');
+ if ( $user->id ) {
+ push @warns, "Found 'Nobody' user in the DB.";
+ }
+ else {
+ my ( $val, $msg ) = $user->Create(
+ Name => 'Nobody',
+ RealName => 'Nobody in particular',
+ Comments => 'Do not delete or modify this user. It is integral '
+ .'to RT\'s internal data structures',
+ Privileged => 0,
+ );
+ return ($val, $msg) unless $val;
+ }
+
+ if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
+ push @warns, "User 'Nobody' has global OwnTicket right.";
+ } else {
+ my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
+ Right => 'OwnTicket',
+ Object => $RT::System,
+ );
+ return ($val, $msg) unless $val;
+ }
+ }
+
+ # rerun to get init Nobody as well
+ RT::InitSystemObjects();
+
+ # system role groups
+ foreach my $name (qw(Owner Requestor Cc AdminCc)) {
+ my $group = RT->System->RoleGroup( $name );
+ if ( $group->id ) {
+ push @warns, "System role '$name' already exists.";
+ next;
+ }
+
+ $group = RT::Group->new( RT->SystemUser );
+ my ( $val, $msg ) = $group->CreateRoleGroup(
+ Name => $name,
+ Object => RT->System,
+ Description => 'SystemRolegroup for internal use', # loc
+ InsideTransaction => 0,
+ );
+ return ($val, $msg) unless $val;
+ }
+
+ push @warns, "You appear to have a functional RT database."
+ if @warns;
+
+ return (1, join "\n", @warns);
+}
+
+=head2 InsertData
+
+Load some sort of data into the database, takes path to a file.
+
+=cut
+
+sub InsertData {
+ my $self = shift;
+ my $datafile = shift;
+ my $root_password = shift;
+ my %args = (
+ disconnect_after => 1,
+ @_
+ );
+
+ # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+ our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
+ @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
+ local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
+ @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
+
+ local $@;
+ $RT::Logger->debug("Going to load '$datafile' data file");
+ eval { require $datafile }
+ or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
+
+ if ( @Initial ) {
+ $RT::Logger->debug("Running initial actions...");
+ foreach ( @Initial ) {
+ local $@;
+ eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
+ }
+ $RT::Logger->debug("Done.");
+ }
+ if ( @Groups ) {
+ $RT::Logger->debug("Creating groups...");
+ foreach my $item (@Groups) {
+ my $attributes = delete $item->{ Attributes };
+ my $new_entry = RT::Group->new( RT->SystemUser );
+ $item->{'Domain'} ||= 'UserDefined';
+ my $member_of = delete $item->{'MemberOf'};
+ my $members = delete $item->{'Members'};
+ my ( $return, $msg ) = $new_entry->_Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ next;
+ } else {
+ $RT::Logger->debug($return .".");
+ $_->{Object} = $new_entry for @{$attributes || []};
+ push @Attributes, @{$attributes || []};
+ }
+ if ( $member_of ) {
+ $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
+ foreach( @$member_of ) {
+ my $parent = RT::Group->new(RT->SystemUser);
+ if ( ref $_ eq 'HASH' ) {
+ $parent->LoadByCols( %$_ );
+ }
+ elsif ( !ref $_ ) {
+ $parent->LoadUserDefinedGroup( $_ );
+ }
+ else {
+ $RT::Logger->error(
+ "(Error: wrong format of MemberOf field."
+ ." Should be name of user defined group or"
+ ." hash reference with 'column => value' pairs."
+ ." Use array reference to add to multiple groups)"
+ );
+ next;
+ }
+ unless ( $parent->Id ) {
+ $RT::Logger->error("(Error: couldn't load group to add member)");
+ next;
+ }
+ my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
+ push @Members, map { +{Group => $new_entry->id,
+ Class => "RT::User", Name => $_} }
+ @{ $members->{Users} || [] };
+ push @Members, map { +{Group => $new_entry->id,
+ Class => "RT::Group", Name => $_} }
+ @{ $members->{Groups} || [] };
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Users ) {
+ $RT::Logger->debug("Creating users...");
+ foreach my $item (@Users) {
+ my $member_of = delete $item->{'MemberOf'};
+ if ( $item->{'Name'} eq 'root' && $root_password ) {
+ $item->{'Password'} = $root_password;
+ }
+ my $attributes = delete $item->{ Attributes };
+ my $new_entry = RT::User->new( RT->SystemUser );
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ $_->{Object} = $new_entry for @{$attributes || []};
+ push @Attributes, @{$attributes || []};
+ }
+ if ( $member_of ) {
+ $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
+ foreach( @$member_of ) {
+ my $parent = RT::Group->new($RT::SystemUser);
+ if ( ref $_ eq 'HASH' ) {
+ $parent->LoadByCols( %$_ );
+ }
+ elsif ( !ref $_ ) {
+ $parent->LoadUserDefinedGroup( $_ );
+ }
+ else {
+ $RT::Logger->error(
+ "(Error: wrong format of MemberOf field."
+ ." Should be name of user defined group or"
+ ." hash reference with 'column => value' pairs."
+ ." Use array reference to add to multiple groups)"
+ );
+ next;
+ }
+ unless ( $parent->Id ) {
+ $RT::Logger->error("(Error: couldn't load group to add member)");
+ next;
+ }
+ my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Members ) {
+ $RT::Logger->debug("Adding users and groups to groups...");
+ for my $item (@Members) {
+ my $group = RT::Group->new(RT->SystemUser);
+ $group->LoadUserDefinedGroup( delete $item->{Group} );
+ unless ($group->Id) {
+ RT->Logger->error("Unable to find group '$group' to add members to");
+ next;
+ }
+
+ my $class = delete $item->{Class} || 'RT::User';
+ my $member = $class->new( RT->SystemUser );
+ $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
+ $member->LoadByCols( %$item );
+ unless ($member->Id) {
+ RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
+ next;
+ }
+
+ my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
+ if ( @Queues ) {
+ $RT::Logger->debug("Creating queues...");
+ for my $item (@Queues) {
+ my $attributes = delete $item->{ Attributes };
+ my $new_entry = RT::Queue->new(RT->SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ $_->{Object} = $new_entry for @{$attributes || []};
+ push @Attributes, @{$attributes || []};
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @CustomFields ) {
+ $RT::Logger->debug("Creating custom fields...");
+ for my $item ( @CustomFields ) {
+ my $attributes = delete $item->{ Attributes };
+ my $new_entry = RT::CustomField->new( RT->SystemUser );
+ my $values = delete $item->{'Values'};
+
+ # Back-compat for the old "Queue" argument
+ if ( exists $item->{'Queue'} ) {
+ $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
+ $RT::Logger->warn("Queue provided for non-ticket custom field")
+ unless $item->{'LookupType'} =~ /^RT::Queue-/;
+ $item->{'ApplyTo'} = delete $item->{'Queue'};
+ }
+
+ my $apply_to = delete $item->{'ApplyTo'};
+
+ if ( $item->{'BasedOn'} ) {
+ if ( $item->{'BasedOn'} =~ /^\d+$/) {
+ # Already have an ID -- should be fine
+ } elsif ( $item->{'LookupType'} ) {
+ my $basedon = RT::CustomField->new($RT::SystemUser);
+ my ($ok, $msg ) = $basedon->LoadByCols(
+ Name => $item->{'BasedOn'},
+ LookupType => $item->{'LookupType'},
+ Disabled => 0 );
+ if ($ok) {
+ $item->{'BasedOn'} = $basedon->Id;
+ } else {
+ $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
+ delete $item->{'BasedOn'};
+ }
+ } else {
+ $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
+ delete $item->{'BasedOn'};
+ }
+
+ }
+
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless( $return ) {
+ $RT::Logger->error( $msg );
+ next;
+ }
+
+ foreach my $value ( @{$values} ) {
+ ( $return, $msg ) = $new_entry->AddValue(%$value);
+ $RT::Logger->error( $msg ) unless $return;
+ }
+
+ my $class = $new_entry->RecordClassFromLookupType;
+ if ($class) {
+ if ($new_entry->IsOnlyGlobal and $apply_to) {
+ $RT::Logger->warn("ApplyTo provided for global custom field ".$new_entry->Name );
+ undef $apply_to;
+ }
+ if ( !$apply_to ) {
+ # Apply to all by default
+ my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
+ ( $return, $msg) = $ocf->Create( CustomField => $new_entry->Id );
+ $RT::Logger->error( $msg ) unless $return and $ocf->Id;
+ } else {
+ $apply_to = [ $apply_to ] unless ref $apply_to;
+ for my $name ( @{ $apply_to } ) {
+ my $obj = $class->new(RT->SystemUser);
+ $obj->Load($name);
+ if ( $obj->Id ) {
+ my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
+ ( $return, $msg ) = $ocf->Create(
+ CustomField => $new_entry->Id,
+ ObjectId => $obj->Id,
+ );
+ $RT::Logger->error( $msg ) unless $return and $ocf->Id;
+ } else {
+ $RT::Logger->error("Could not find $class $name to apply ".$new_entry->Name." to" );
+ }
+ }
+ }
+ }
+
+ $_->{Object} = $new_entry for @{$attributes || []};
+ push @Attributes, @{$attributes || []};
+ }
+
+ $RT::Logger->debug("done.");
+ }
+ if ( @ACL ) {
+ $RT::Logger->debug("Creating ACL...");
+ for my $item (@ACL) {
+
+ my ($princ, $object);
+
+ # Global rights or Queue rights?
+ if ( $item->{'CF'} ) {
+ $object = RT::CustomField->new( RT->SystemUser );
+ my @columns = ( Name => $item->{'CF'} );
+ push @columns, LookupType => $item->{'LookupType'} if $item->{'LookupType'};
+ push @columns, ObjectId => $item->{'ObjectId'} if $item->{'ObjectId'};
+ push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
+ my ($ok, $msg) = $object->LoadByName( @columns );
+ unless ( $ok ) {
+ RT->Logger->error("Unable to load CF ".$item->{CF}.": $msg");
+ next;
+ }
+ } elsif ( $item->{'Queue'} ) {
+ $object = RT::Queue->new(RT->SystemUser);
+ my ($ok, $msg) = $object->Load( $item->{'Queue'} );
+ unless ( $ok ) {
+ RT->Logger->error("Unable to load queue ".$item->{Queue}.": $msg");
+ next;
+ }
+ } elsif ( $item->{ObjectType} and $item->{ObjectId}) {
+ $object = $item->{ObjectType}->new(RT->SystemUser);
+ my ($ok, $msg) = $object->Load( $item->{ObjectId} );
+ unless ( $ok ) {
+ RT->Logger->error("Unable to load ".$item->{ObjectType}." ".$item->{ObjectId}.": $msg");
+ next;
+ }
+ } else {
+ $object = $RT::System;
+ }
+
+ # Group rights or user rights?
+ if ( $item->{'GroupDomain'} ) {
+ $princ = RT::Group->new(RT->SystemUser);
+ if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
+ $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
+ } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
+ $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
+ } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
+ $princ->LoadRoleGroup( Object => RT->System, Name => $item->{'GroupType'} );
+ } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
+ $item->{'Queue'} )
+ {
+ $princ->LoadRoleGroup( Object => $object, Name => $item->{'GroupType'} );
+ } else {
+ $princ->Load( $item->{'GroupId'} );
+ }
+ unless ( $princ->Id ) {
+ RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
+ next;
+ }
+ } else {
+ $princ = RT::User->new(RT->SystemUser);
+ my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
+ unless ( $ok ) {
+ RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
+ next;
+ }
+ }
+
+ # Grant it
+ my @rights = ref($item->{'Right'}) eq 'ARRAY' ? @{$item->{'Right'}} : $item->{'Right'};
+ foreach my $right ( @rights ) {
+ my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+ Right => $right,
+ Object => $object
+ );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+
+ if ( @ScripActions ) {
+ $RT::Logger->debug("Creating ScripActions...");
+
+ for my $item (@ScripActions) {
+ my $new_entry = RT::ScripAction->new(RT->SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+
+ $RT::Logger->debug("done.");
+ }
+
+ if ( @ScripConditions ) {
+ $RT::Logger->debug("Creating ScripConditions...");
+
+ for my $item (@ScripConditions) {
+ my $new_entry = RT::ScripCondition->new(RT->SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+
+ $RT::Logger->debug("done.");
+ }
+
+ if ( @Templates ) {
+ $RT::Logger->debug("Creating templates...");
+
+ for my $item (@Templates) {
+ my $new_entry = RT::Template->new(RT->SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Scrips ) {
+ $RT::Logger->debug("Creating scrips...");
+
+ for my $item (@Scrips) {
+ my $new_entry = RT::Scrip->new(RT->SystemUser);
+
+ my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
+ push @queues, 0 unless @queues; # add global queue at least
+
+ my ( $return, $msg ) = $new_entry->Create( %$item, Queue => shift @queues );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ next;
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ foreach my $q ( @queues ) {
+ my ($return, $msg) = $new_entry->AddToObject(
+ ObjectId => $q,
+ Stage => $item->{'Stage'},
+ );
+ $RT::Logger->error( "Couldn't apply scrip to $q: $msg" )
+ unless $return;
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Attributes ) {
+ $RT::Logger->debug("Creating attributes...");
+ my $sys = RT::System->new(RT->SystemUser);
+
+ for my $item (@Attributes) {
+ my $obj = delete $item->{Object};
+
+ if ( ref $obj eq 'CODE' ) {
+ $obj = $obj->();
+ }
+
+ $obj ||= $sys;
+ my ( $return, $msg ) = $obj->AddAttribute (%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Final ) {
+ $RT::Logger->debug("Running final actions...");
+ for ( @Final ) {
+ local $@;
+ eval { $_->(); };
+ $RT::Logger->error( "Failed to run one of final actions: $@" )
+ if $@;
+ }
+ $RT::Logger->debug("done.");
+ }
+
+ # XXX: This disconnect doesn't really belong here; it's a relict from when
+ # this method was extracted from rt-setup-database. However, too much
+ # depends on it to change without significant testing. At the very least,
+ # we can provide a way to skip the side-effect.
+ if ( $args{disconnect_after} ) {
+ my $db_type = RT->Config->Get('DatabaseType');
+ $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+ }
+
+ $RT::Logger->debug("Done setting up database content.");
+
+# TODO is it ok to return 1 here? If so, the previous codes in this sub
+# should return (0, $msg) if error happens instead of just warning.
+# anyway, we need to return something here to tell if everything is ok
+ return( 1, 'Done inserting data' );
+}
+
+=head2 ACLEquivGroupId
+
+Given a userid, return that user's acl equivalence group
+
+=cut
+
+sub ACLEquivGroupId {
+ my $id = shift;
+
+ my $cu = RT->SystemUser;
+ unless ( $cu ) {
+ require RT::CurrentUser;
+ $cu = RT::CurrentUser->new;
+ $cu->LoadByName('RT_System');
+ warn "Couldn't load RT_System user" unless $cu->id;
+ }
+
+ my $equiv_group = RT::Group->new( $cu );
+ $equiv_group->LoadACLEquivalenceGroup( $id );
+ return $equiv_group->Id;
+}
+
+=head2 QueryHistory
+
+Returns the SQL query history associated with this handle. The top level array
+represents a lists of request. Each request is a hash with metadata about the
+request (such as the URL) and a list of queries. You'll probably not be using this.
+
+=cut
+
+sub QueryHistory {
+ my $self = shift;
+
+ return $self->{QueryHistory};