diff options
Diffstat (limited to 'rt/lib')
| -rwxr-xr-x | rt/lib/RT/Action/SendEmail.pm | 54 | ||||
| -rw-r--r-- | rt/lib/RT/Attachment_Overlay.pm | 49 | ||||
| -rw-r--r-- | rt/lib/RT/Crypt/GnuPG.pm | 28 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Email.pm | 54 | ||||
| -rwxr-xr-x | rt/lib/RT/Interface/Email/Auth/GnuPG.pm | 3 | ||||
| -rw-r--r-- | rt/lib/RT/Interface/Web.pm | 127 | ||||
| -rw-r--r-- | rt/lib/RT/Queue_Overlay.pm | 40 | ||||
| -rw-r--r-- | rt/lib/RT/Template_Overlay.pm | 1 | ||||
| -rw-r--r-- | rt/lib/RT/User_Overlay.pm | 1 |
9 files changed, 259 insertions, 98 deletions
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index a98a7647d..189b999a8 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -100,47 +100,31 @@ activated in the config. sub Commit { my $self = shift; - $self->DeferDigestRecipients() if RT->Config->Get('RecordOutgoingEmail'); + return abs $self->SendMessage( $self->TemplateObj->MIMEObj ) + unless RT->Config->Get('RecordOutgoingEmail'); + + $self->DeferDigestRecipients(); my $message = $self->TemplateObj->MIMEObj; my $orig_message; - if ( RT->Config->Get('RecordOutgoingEmail') - && RT->Config->Get('GnuPG')->{'Enable'} ) - { - - # it's hacky, but we should know if we're going to crypt things - my $attachment = $self->TransactionObj->Attachments->First; - - my %crypt; - foreach my $argument (qw(Sign Encrypt)) { - if ( $attachment - && defined $attachment->GetHeader("X-RT-$argument") ) - { - $crypt{$argument} = $attachment->GetHeader("X-RT-$argument"); - } else { - $crypt{$argument} = $self->TicketObj->QueueObj->$argument(); - } - } - if ( $crypt{'Sign'} || $crypt{'Encrypt'} ) { - $orig_message = $message->dup; - } - } + $orig_message = $message->dup if RT::Interface::Email::WillSignEncrypt( + Attachment => $self->TransactionObj->Attachments->First, + Ticket => $self->TicketObj, + ); my ($ret) = $self->SendMessage($message); - if ( $ret > 0 && RT->Config->Get('RecordOutgoingEmail') ) { - if ($orig_message) { - $message->attach( - Type => 'application/x-rt-original-message', - Disposition => 'inline', - Data => $orig_message->as_string, - ); - } - $self->RecordOutgoingMailTransaction($message); - $self->RecordDeferredRecipients(); + return abs( $ret ) if $ret <= 0; + + if ($orig_message) { + $message->attach( + Type => 'application/x-rt-original-message', + Disposition => 'inline', + Data => $orig_message->as_string, + ); } - - - return ( abs $ret ); + $self->RecordOutgoingMailTransaction($message); + $self->RecordDeferredRecipients(); + return 1; } =head2 Prepare diff --git a/rt/lib/RT/Attachment_Overlay.pm b/rt/lib/RT/Attachment_Overlay.pm index 45a5ab6f1..cdc677094 100644 --- a/rt/lib/RT/Attachment_Overlay.pm +++ b/rt/lib/RT/Attachment_Overlay.pm @@ -550,8 +550,8 @@ sub DelHeader { my $newheader = ''; foreach my $line ($self->_SplitHeaders) { - next if $line =~ /^\Q$tag\E:\s+(.*)$/is; - $newheader .= "$line\n"; + next if $line =~ /^\Q$tag\E:\s+/i; + $newheader .= "$line\n"; } return $self->__Set( Field => 'Headers', Value => $newheader); } @@ -567,9 +567,7 @@ sub AddHeader { my $newheader = $self->__Value( 'Headers' ); while ( my ($tag, $value) = splice @_, 0, 2 ) { - $value = '' unless defined $value; - $value =~ s/\s+$//s; - $value =~ s/\r+\n/\n /g; + $value = $self->_CanonicalizeHeaderValue($value); $newheader .= "$tag: $value\n"; } return $self->__Set( Field => 'Headers', Value => $newheader); @@ -582,24 +580,39 @@ Replace or add a Header to the attachment's headers. =cut sub SetHeader { - my $self = shift; - my $tag = shift; + my $self = shift; + my $tag = shift; + my $value = $self->_CanonicalizeHeaderValue(shift); + my $replaced = 0; my $newheader = ''; - foreach my $line ($self->_SplitHeaders) { - if (defined $tag and $line =~ /^\Q$tag\E:\s+(.*)$/i) { - $newheader .= "$tag: $_[0]\n"; - undef $tag; + foreach my $line ( $self->_SplitHeaders ) { + if ( $line =~ /^\Q$tag\E:\s+/i ) { + # replace first instance, skip all the rest + unless ($replaced) { + $newheader .= "$tag: $value\n"; + $replaced = 1; + } + } else { + $newheader .= "$line\n"; } - else { - $newheader .= "$line\n"; - } } - $newheader .= "$tag: $_[0]\n" if defined $tag; + $newheader .= "$tag: $value\n" unless $replaced; $self->__Set( Field => 'Headers', Value => $newheader); } +sub _CanonicalizeHeaderValue { + my $self = shift; + my $value = shift; + + $value = '' unless defined $value; + $value =~ s/\s+$//s; + $value =~ s/\r*\n/\n /g; + + return $value; +} + =head2 SplitHeaders Returns an array of this attachment object's headers, with one header @@ -626,6 +639,12 @@ sub _SplitHeaders { my $self = shift; my $headers = (shift || $self->SUPER::Headers()); my @headers; + # XXX TODO: splitting on \n\w is _wrong_ as it treats \n[ as a valid + # continuation, which it isn't. The correct split pattern, per RFC 2822, + # is /\n(?=[^ \t]|\z)/. That is, only "\n " or "\n\t" is a valid + # continuation. Older values of X-RT-GnuPG-Status contain invalid + # continuations and rely on this bogus split pattern, however, so it is + # left as-is for now. for (split(/\n(?=\w|\z)/,$headers)) { push @headers, $_; diff --git a/rt/lib/RT/Crypt/GnuPG.pm b/rt/lib/RT/Crypt/GnuPG.pm index 314e6cc38..91f974c06 100644 --- a/rt/lib/RT/Crypt/GnuPG.pm +++ b/rt/lib/RT/Crypt/GnuPG.pm @@ -896,6 +896,19 @@ sub FindProtectedParts { $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" ); return (); } + + # Deal with "partitioned" PGP mail, which (contrary to common + # sense) unnecessarily applies a base64 transfer encoding to PGP + # mail (whose content is already base64-encoded). + if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) { + pipe( my ($read_decoded, $write_decoded) ); + my $decoder = MIME::Decoder->new( $entity->head->mime_encoding ); + if ($decoder) { + eval { $decoder->decode($io, $write_decoded) }; + $io = $read_decoded; + } + } + while ( defined($_ = $io->getline) ) { next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/; my $type = $1? 'signed': 'encrypted'; @@ -1060,9 +1073,13 @@ sub VerifyDecrypt { } if ( $args{'SetStatus'} || $args{'AddStatus'} ) { my $method = $args{'AddStatus'} ? 'add' : 'set'; + # Let the header be modified so continuations are handled + my $modify = $status_on->head->modify; + $status_on->head->modify(1); $status_on->head->$method( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} ); + $status_on->head->modify($modify); } } foreach my $item( grep $_->{'Type'} eq 'encrypted', @protected ) { @@ -1079,9 +1096,13 @@ sub VerifyDecrypt { } if ( $args{'SetStatus'} || $args{'AddStatus'} ) { my $method = $args{'AddStatus'} ? 'add' : 'set'; + # Let the header be modified so continuations are handled + my $modify = $status_on->head->modify; + $status_on->head->modify(1); $status_on->head->$method( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} ); + $status_on->head->modify($modify); } } return @res; @@ -1673,6 +1694,7 @@ my %ignore_keyword = map { $_ => 1 } qw( BEGIN_ENCRYPTION SIG_ID VALIDSIG ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE + DECRYPTION_INFO ); sub ParseStatus { @@ -2096,7 +2118,9 @@ sub GetKeysInfo { eval { local $SIG{'CHLD'} = 'DEFAULT'; my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys'; - my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email? (command_args => $email) : () ) }; + my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email + ? (command_args => [ "--", $email]) + : () ) }; close $handle{'stdin'}; waitpid $pid, 0; }; @@ -2291,7 +2315,7 @@ sub DeleteKey { my $pid = safe_run_child { $gnupg->wrap_call( handles => $handles, commands => ['--delete-secret-and-public-key'], - command_args => [$key], + command_args => ["--", $key], ) }; close $handle{'stdin'}; while ( my $str = readline $handle{'status'} ) { diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm index 37b1545d5..678f1dbdd 100755 --- a/rt/lib/RT/Interface/Email.pm +++ b/rt/lib/RT/Interface/Email.pm @@ -318,6 +318,35 @@ header field then it's value is used =cut +sub WillSignEncrypt { + my %args = @_; + my $attachment = delete $args{Attachment}; + my $ticket = delete $args{Ticket}; + + if ( not RT->Config->Get('GnuPG')->{'Enable'} ) { + $args{Sign} = $args{Encrypt} = 0; + return wantarray ? %args : 0; + } + + for my $argument ( qw(Sign Encrypt) ) { + next if defined $args{ $argument }; + + if ( $attachment and defined $attachment->GetHeader("X-RT-$argument") ) { + $args{$argument} = $attachment->GetHeader("X-RT-$argument"); + } elsif ( $ticket and $argument eq "Encrypt" ) { + $args{Encrypt} = $ticket->QueueObj->Encrypt(); + } elsif ( $ticket and $argument eq "Sign" ) { + # Note that $queue->Sign is UI-only, and that all + # UI-generated messages explicitly set the X-RT-Crypt header + # to 0 or 1; thus this path is only taken for messages + # generated _not_ via the web UI. + $args{Sign} = $ticket->QueueObj->SignAuto(); + } + } + + return wantarray ? %args : ($args{Sign} || $args{Encrypt}); +} + sub SendEmail { my (%args) = ( Entity => undef, @@ -366,23 +395,12 @@ sub SendEmail { } if ( RT->Config->Get('GnuPG')->{'Enable'} ) { - my %crypt; - - my $attachment; - $attachment = $TransactionObj->Attachments->First - if $TransactionObj; - - foreach my $argument ( qw(Sign Encrypt) ) { - next if defined $args{ $argument }; - - if ( $attachment && defined $attachment->GetHeader("X-RT-$argument") ) { - $crypt{$argument} = $attachment->GetHeader("X-RT-$argument"); - } elsif ( $TicketObj ) { - $crypt{$argument} = $TicketObj->QueueObj->$argument(); - } - } - - my $res = SignEncrypt( %args, %crypt ); + %args = WillSignEncrypt( + %args, + Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef, + Ticket => $TicketObj, + ); + my $res = SignEncrypt( %args ); return $res unless $res > 0; } @@ -905,7 +923,7 @@ sub EncodeToMIME { return ($value); } - return ($value) unless $value =~ /[^\x20-\x7e]/; + return ($value) if $value =~ /^(?:[\t\x20-\x7e]|\x0D*\x0A[ \t])+$/s; $value =~ s/\s+$//; diff --git a/rt/lib/RT/Interface/Email/Auth/GnuPG.pm b/rt/lib/RT/Interface/Email/Auth/GnuPG.pm index 6d43b9610..846c01353 100755 --- a/rt/lib/RT/Interface/Email/Auth/GnuPG.pm +++ b/rt/lib/RT/Interface/Email/Auth/GnuPG.pm @@ -77,8 +77,9 @@ sub GetCurrentUser { foreach my $p ( $args{'Message'}->parts_DFS ) { $p->head->delete($_) for qw( - X-RT-GnuPG-Status X-RT-Incoming-Encrypton + X-RT-GnuPG-Status X-RT-Incoming-Encryption X-RT-Incoming-Signature X-RT-Privacy + X-RT-Sign X-RT-Encrypt ); } diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 61c06acb2..a8cffb8b2 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -244,12 +244,12 @@ sub HandleRequest { } # Specially handle /index.html so that we get a nicer URL elsif ( $m->request_comp->path eq '/index.html' ) { - my $next = SetNextPage(RT->Config->Get('WebURL')); + my $next = SetNextPage($ARGS); $m->comp('/NoAuth/Login.html', next => $next, actions => [$msg]); $m->abort; } else { - TangentForLogin(results => ($msg ? LoginError($msg) : undef)); + TangentForLogin($ARGS, results => ($msg ? LoginError($msg) : undef)); } } } @@ -298,7 +298,7 @@ sub LoginError { return $key; } -=head2 SetNextPage [PATH] +=head2 SetNextPage ARGSRef [PATH] Intuits and stashes the next page in the sesssion hash. If PATH is specified, uses that instead of the value of L<IntuitNextPage()>. Returns @@ -307,24 +307,68 @@ the hash value. =cut sub SetNextPage { - my $next = shift || IntuitNextPage(); + my $ARGS = shift; + my $next = $_[0] ? $_[0] : IntuitNextPage(); my $hash = Digest::MD5::md5_hex($next . $$ . rand(1024)); + my $page = { url => $next }; + + # If an explicit URL was passed and we didn't IntuitNextPage, then + # IsPossibleCSRF below is almost certainly unrelated to the actual + # destination. Currently explicit next pages aren't used in RT, but the + # API is available. + if (not $_[0] and RT->Config->Get("RestrictReferrer")) { + # This isn't really CSRF, but the CSRF heuristics are useful for catching + # requests which may have unintended side-effects. + my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS); + if ($is_csrf) { + RT->Logger->notice( + "Marking original destination as having side-effects before redirecting for login.\n" + ."Request: $next\n" + ."Reason: " . HTML::Mason::Commands::loc($msg, @loc) + ); + $page->{'HasSideEffects'} = [$msg, @loc]; + } + } - $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next; + $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $page; $HTML::Mason::Commands::session{'i'}++; return $hash; } +=head2 FetchNextPage HASHKEY + +Returns the stashed next page hashref for the given hash. + +=cut + +sub FetchNextPage { + my $hash = shift || ""; + return $HTML::Mason::Commands::session{'NextPage'}->{$hash}; +} + +=head2 RemoveNextPage HASHKEY + +Removes the stashed next page for the given hash and returns it. + +=cut + +sub RemoveNextPage { + my $hash = shift || ""; + return delete $HTML::Mason::Commands::session{'NextPage'}->{$hash}; +} -=head2 TangentForLogin [HASH] +=head2 TangentForLogin ARGSRef [HASH] Redirects to C</NoAuth/Login.html>, setting the value of L<IntuitNextPage> as -the next page. Optionally takes a hash which is dumped into query params. +the next page. Takes a hashref of request %ARGS as the first parameter. +Optionally takes all other parameters as a hash which is dumped into query +params. =cut sub TangentForLogin { - my $hash = SetNextPage(); + my $ARGS = shift; + my $hash = SetNextPage($ARGS); my %query = (@_, next => $hash); my $login = RT->Config->Get('WebURL') . 'NoAuth/Login.html?'; $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query); @@ -339,8 +383,9 @@ calls L<TangentForLogin> with the appropriate results key. =cut sub TangentForLoginWithError { - my $key = LoginError(HTML::Mason::Commands::loc(@_)); - TangentForLogin( results => $key ); + my $ARGS = shift; + my $key = LoginError(HTML::Mason::Commands::loc(@_)); + TangentForLogin( $ARGS, results => $key ); } =head2 IntuitNextPage @@ -529,6 +574,8 @@ sub AttemptExternalAuth { $user =~ s/^\Q$NodeName\E\\//i; } + my $next = RemoveNextPage($ARGS->{'next'}); + $next = $next->{'url'} if ref $next; InstantiateNewSession() unless _UserLoggedIn; $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new(); $HTML::Mason::Commands::session{'CurrentUser'}->$load_method($user); @@ -567,7 +614,7 @@ sub AttemptExternalAuth { delete $HTML::Mason::Commands::session{'CurrentUser'}; if (RT->Config->Get('WebFallbackToInternalAuth')) { - TangentForLoginWithError('Cannot create user: [_1]', $msg); + TangentForLoginWithError($ARGS, 'Cannot create user: [_1]', $msg); } else { $m->abort(); } @@ -576,18 +623,27 @@ sub AttemptExternalAuth { if ( _UserLoggedIn() ) { $m->callback( %$ARGS, CallbackName => 'ExternalAuthSuccessfulLogin', CallbackPage => '/autohandler' ); + # It is possible that we did a redirect to the login page, + # if the external auth allows lack of auth through with no + # REMOTE_USER set, instead of forcing a "permission + # denied" message. Honor the $next. + Redirect($next) if $next; + # Unlike AttemptPasswordAuthentication below, we do not + # force a redirect to / if $next is not set -- otherwise, + # straight-up external auth would always redirect to / + # when you first hit it. } else { delete $HTML::Mason::Commands::session{'CurrentUser'}; $user = $orig_user; - if ( RT->Config->Get('WebExternalOnly') ) { - TangentForLoginWithError('You are not an authorized user'); + unless ( RT->Config->Get('WebFallbackToInternalAuth') ) { + TangentForLoginWithError($ARGS, 'You are not an authorized user'); } } } elsif ( RT->Config->Get('WebFallbackToInternalAuth') ) { unless ( defined $HTML::Mason::Commands::session{'CurrentUser'} ) { # XXX unreachable due to prior defaulting in HandleRequest (check c34d108) - TangentForLoginWithError('You are not an authorized user'); + TangentForLoginWithError($ARGS, 'You are not an authorized user'); } } else { @@ -618,7 +674,8 @@ sub AttemptPasswordAuthentication { # It's important to nab the next page from the session before we blow # the session away - my $next = delete $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''}; + my $next = RemoveNextPage($ARGS->{'next'}); + $next = $next->{'url'} if ref $next; InstantiateNewSession(); $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj; @@ -1048,6 +1105,13 @@ our %is_whitelisted_component = ( '/Search/Simple.html' => 1, ); +# Components which are blacklisted from automatic, argument-based whitelisting. +# These pages are not idempotent when called with just an id. +our %is_blacklisted_component = ( + # Takes only id and toggles bookmark state + '/Helpers/Toggle/TicketBookmark' => 1, +); + sub IsCompCSRFWhitelisted { my $comp = shift; my $ARGS = shift; @@ -1070,6 +1134,10 @@ sub IsCompCSRFWhitelisted { delete $args{pass}; } + # Some pages aren't idempotent even with safe args like id; blacklist + # them from the automatic whitelisting below. + return 0 if $is_blacklisted_component{$comp}; + # Eliminate arguments that do not indicate an effectful request. # For example, "id" is acceptable because that is how RT retrieves a # record. @@ -1249,6 +1317,29 @@ sub MaybeShowInterstitialCSRFPage { # Calls abort, never gets here } +our @POTENTIAL_PAGE_ACTIONS = ( + qr'/Ticket/Create.html' => "create a ticket", # loc + qr'/Ticket/' => "update a ticket", # loc + qr'/Admin/' => "modify RT's configuration", # loc + qr'/Approval/' => "update an approval", # loc + qr'/Dashboards/' => "modify a dashboard", # loc + qr'/m/ticket/' => "update a ticket", # loc + qr'Prefs' => "modify your preferences", # loc + qr'/Search/' => "modify or access a search", # loc + qr'/SelfService/Create' => "create a ticket", # loc + qr'/SelfService/' => "update a ticket", # loc +); + +sub PotentialPageAction { + my $page = shift; + my @potentials = @POTENTIAL_PAGE_ACTIONS; + while (my ($pattern, $result) = splice @potentials, 0, 2) { + return HTML::Mason::Commands::loc($result) + if $page =~ $pattern; + } + return ""; +} + package HTML::Mason::Commands; use vars qw/$r $m %session/; @@ -1397,10 +1488,8 @@ sub CreateTicket { } } - foreach my $argument (qw(Encrypt Sign)) { - $MIMEObj->head->add( - "X-RT-$argument" => Encode::encode_utf8( $ARGS{$argument} ) - ) if defined $ARGS{$argument}; + for my $argument (qw(Encrypt Sign)) { + $MIMEObj->head->replace( "X-RT-$argument" => $ARGS{$argument} ? 1 : 0 ); } my %create_args = ( diff --git a/rt/lib/RT/Queue_Overlay.pm b/rt/lib/RT/Queue_Overlay.pm index 0c8f16899..a3482938f 100644 --- a/rt/lib/RT/Queue_Overlay.pm +++ b/rt/lib/RT/Queue_Overlay.pm @@ -336,6 +336,7 @@ sub Create { FinalPriority => 0, DefaultDueIn => 0, Sign => undef, + SignAuto => undef, Encrypt => undef, _RecordTransaction => 1, @_ @@ -370,14 +371,11 @@ sub Create { } $RT::Handle->Commit; - if ( defined $args{'Sign'} ) { - my ($status, $msg) = $self->SetSign( $args{'Sign'} ); - $RT::Logger->error("Couldn't set attribute 'Sign': $msg") - unless $status; - } - if ( defined $args{'Encrypt'} ) { - my ($status, $msg) = $self->SetEncrypt( $args{'Encrypt'} ); - $RT::Logger->error("Couldn't set attribute 'Encrypt': $msg") + for my $attr (qw/Sign SignAuto Encrypt/) { + next unless defined $args{$attr}; + my $set = "Set" . $attr; + my ($status, $msg) = $self->$set( $args{$attr} ); + $RT::Logger->error("Couldn't set attribute '$attr': $msg") unless $status; } @@ -524,6 +522,32 @@ sub SetSign { return ($status, $self->loc('Signing disabled')); } +sub SignAuto { + my $self = shift; + my $value = shift; + + return undef unless $self->CurrentUserHasRight('SeeQueue'); + my $attr = $self->FirstAttribute('SignAuto') or return 0; + return $attr->Content; +} + +sub SetSignAuto { + my $self = shift; + my $value = shift; + + return ( 0, $self->loc('Permission Denied') ) + unless $self->CurrentUserHasRight('AdminQueue'); + + my ($status, $msg) = $self->SetAttribute( + Name => 'SignAuto', + Description => 'Sign auto-generated outgoing messages', + Content => $value, + ); + return ($status, $msg) unless $status; + return ($status, $self->loc('Signing enabled')) if $value; + return ($status, $self->loc('Signing disabled')); +} + sub Encrypt { my $self = shift; my $value = shift; diff --git a/rt/lib/RT/Template_Overlay.pm b/rt/lib/RT/Template_Overlay.pm index 21cb97a9f..963a87b5a 100644 --- a/rt/lib/RT/Template_Overlay.pm +++ b/rt/lib/RT/Template_Overlay.pm @@ -383,6 +383,7 @@ sub _Parse { # Unfold all headers $self->{'MIMEObj'}->head->unfold; + $self->{'MIMEObj'}->head->modify(1); return ( 1, $self->loc("Template parsed") ); diff --git a/rt/lib/RT/User_Overlay.pm b/rt/lib/RT/User_Overlay.pm index 2b50fac82..998f849e5 100644 --- a/rt/lib/RT/User_Overlay.pm +++ b/rt/lib/RT/User_Overlay.pm @@ -94,6 +94,7 @@ sub _OverlayAccessible { AuthSystem => { public => 1, admin => 1 }, Gecos => { public => 1, admin => 1 }, PGPKey => { public => 1, admin => 1 }, + PrivateKey => { admin => 1 }, } } |
