[Rt-commit] r10508 - in rt/branches/3.7-EXPERIMENTAL: . etc html/Ticket html/Ticket/Elements lib/RT
ruz at bestpractical.com
ruz at bestpractical.com
Sat Jan 26 20:01:30 EST 2008
Author: ruz
Date: Sat Jan 26 20:01:30 2008
New Revision: 10508
Added:
rt/branches/3.7-EXPERIMENTAL/html/Ticket/GnuPG.html
Modified:
rt/branches/3.7-EXPERIMENTAL/ (props changed)
rt/branches/3.7-EXPERIMENTAL/etc/RT_Config.pm.in
rt/branches/3.7-EXPERIMENTAL/html/Ticket/Elements/ShowTransaction
rt/branches/3.7-EXPERIMENTAL/lib/RT/Attachment_Overlay.pm
rt/branches/3.7-EXPERIMENTAL/lib/RT/Crypt/GnuPG.pm
Log:
r10492 at cubic-pc (orig r10458): ruz | 2008-01-24 02:13:46 +0300
* add configurable option that allow people to encrypt
attachments right in the DB
Modified: rt/branches/3.7-EXPERIMENTAL/etc/RT_Config.pm.in
==============================================================================
--- rt/branches/3.7-EXPERIMENTAL/etc/RT_Config.pm.in (original)
+++ rt/branches/3.7-EXPERIMENTAL/etc/RT_Config.pm.in Sat Jan 26 20:01:30 2008
@@ -313,6 +313,10 @@
# Set OutgoingMessagesFormat to 'inline' to use inline encryption and
# signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
OutgoingMessagesFormat => 'RFC', # Inline
+
+# If you want to allow people to encrypt attachments inside the DB then
+# set below option to true value
+ AllowEncryptDataInDB => 0,
);
# Options of GnuPG program
Modified: rt/branches/3.7-EXPERIMENTAL/html/Ticket/Elements/ShowTransaction
==============================================================================
--- rt/branches/3.7-EXPERIMENTAL/html/Ticket/Elements/ShowTransaction (original)
+++ rt/branches/3.7-EXPERIMENTAL/html/Ticket/Elements/ShowTransaction Sat Jan 26 20:01:30 2008
@@ -89,6 +89,7 @@
$AttachPath => RT->Config->Get('WebPath')."/Ticket/Attachment"
$UpdatePath => RT->Config->Get('WebPath')."/Ticket/Update.html"
$ForwardPath => RT->Config->Get('WebPath')."/Ticket/Forward.html"
+$EncryptionPath => RT->Config->Get('WebPath')."/Ticket/GnuPG.html"
$EmailRecordPath => RT->Config->Get('WebPath')."/Ticket/ShowEmailRecord.html"
$Attachments => undef
$AttachmentContent => undef
@@ -182,8 +183,9 @@
}
if ( $Attachments->[0] && $ShowTitleBarCommands ) {
- my $can_modify = $Transaction->TicketObj->CurrentUserHasRight('ModifyTicket');
- if ( $can_modify || $Transaction->TicketObj->CurrentUserHasRight('ReplyToTicket') ) {
+ my $ticket = $Transaction->TicketObj;
+ my $can_modify = $ticket->CurrentUserHasRight('ModifyTicket');
+ if ( $can_modify || $ticket->CurrentUserHasRight('ReplyToTicket') ) {
$titlebar_commands .=
"[<a href=\"".$UpdatePath
. "?id=" . $Transaction->Ticket
@@ -192,7 +194,7 @@
. loc('Reply')
. "</a>] ";
}
- if ( $can_modify || $Transaction->TicketObj->CurrentUserHasRight('CommentOnTicket') ) {
+ if ( $can_modify || $ticket->CurrentUserHasRight('CommentOnTicket') ) {
$titlebar_commands .=
"[<a href=\"".$UpdatePath."?id="
. $Transaction->Ticket
@@ -201,13 +203,23 @@
. "&Action=Comment\">"
. loc('Comment') . "</a>]";
}
- if ( $Transaction->TicketObj->CurrentUserHasRight('ForwardMessage') ) {
+ if ( $ticket->CurrentUserHasRight('ForwardMessage') ) {
$titlebar_commands .=
"[<a href=\"". $ForwardPath
. "?id=". $Transaction->Ticket
. "&QuoteTransaction=". $Transaction->Id
. "\">". loc('Forward') . "</a>]";
}
+ if ( $can_modify
+ && RT->Config->Get('GnuPG')->{'Enable'}
+ && RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'}
+ && $ticket->CurrentUserHasRight('ForwardMessage')
+ ) {
+ $titlebar_commands .=
+ "[<a href=\"". $EncryptionPath
+ . "?id=". $Transaction->Id
+ . "\">". loc('Ecnrypt/Decrypt') . "</a>]";
+ }
}
}
</%INIT>
Added: rt/branches/3.7-EXPERIMENTAL/html/Ticket/GnuPG.html
==============================================================================
--- (empty file)
+++ rt/branches/3.7-EXPERIMENTAL/html/Ticket/GnuPG.html Sat Jan 26 20:01:30 2008
@@ -0,0 +1,57 @@
+<& /Elements/Header, Title => $title &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $txn->TicketObj,
+ current_tab => 'Ticket/Encrypt.html?id='. $id,
+ Title => $title,
+&>
+
+% $m->callback( CallbackName => 'BeforeActionList', %ARGS, Actions => \@results, ARGSRef => \%ARGS );
+<& /Elements/ListActions, actions => \@results &>
+<form method="post">
+<a href="<% RT->Config->Get('WebURL') %>/Ticket/Display.html?id=<% $txn->Ticket %>#txn-<% $id %>">
+<% loc('Return back to the ticket') %>
+</a>
+<& /Elements/Submit,
+ Label => ($encrypted? loc('Decrypt'): loc('Encrypt')),
+ Name => ($encrypted? 'Decrypt': 'Encrypt'),
+&>
+</form>
+<%ARGS>
+$id => undef
+$Encrypt => 0
+$Decrypt => 0
+</%ARGS>
+<%INIT>
+my $txn = RT::Transaction->new( $session{'CurrentUser'} );
+$txn->Load( $id );
+unless ( $txn->id ) {
+ Abort(loc("Couldn't load transaction #[_1]", $id));
+}
+$id = $txn->id;
+
+my @results;
+
+my $encrypted = 0;
+
+my $attachments = $txn->Attachments;
+while ( my $attachment = $attachments->Next ) {
+ next unless $attachment->ContentType =~ m{^x-application-rt/gpg-encrypted\b};
+ $encrypted = 1;
+ last;
+}
+$attachments->GotoFirstItem;
+
+if ( $Encrypt || $Decrypt ) {
+ my $done = 1;
+ while ( my $attachment = $attachments->Next ) {
+ my ($status, $msg) = $Decrypt? $attachment->Decrypt : $attachment->Encrypt;
+ push @results, $msg;
+ unless ( $status ) {
+ $done = 0; last;
+ }
+ }
+ $encrypted = !$encrypted if $done;
+}
+
+my $title = loc("Encrypt/Decrypt transaction #[_1] of ticket #[_2]", $id, $txn->Ticket);
+</%INIT>
Modified: rt/branches/3.7-EXPERIMENTAL/lib/RT/Attachment_Overlay.pm
==============================================================================
--- rt/branches/3.7-EXPERIMENTAL/lib/RT/Attachment_Overlay.pm (original)
+++ rt/branches/3.7-EXPERIMENTAL/lib/RT/Attachment_Overlay.pm Sat Jan 26 20:01:30 2008
@@ -607,6 +607,103 @@
}
+sub Encrypt {
+ my $self = shift;
+
+ my $txn = $self->TransactionObj;
+ return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
+ return (0, $self->loc('Permission Denied'))
+ unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
+ return (0, $self->loc('GnuPG integration is disabled'))
+ unless RT->Config->Get('GnuPG')->{'Enable'};
+ return (0, $self->loc('Attachments encryption is disabled'))
+ unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'};
+
+ require RT::Crypt::GnuPG;
+
+ my $type = $self->ContentType;
+ if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+ return (1, $self->loc('Already encrypted'));
+ } elsif ( $type =~ /^multipart\//i ) {
+ return (1, $self->loc('No need to encrypt'));
+ } else {
+ $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
+ }
+
+ my $queue = $txn->TicketObj->QueueObj;
+ my $encrypt_for;
+ foreach my $address ( grep $_,
+ $queue->CorrespondAddress,
+ $queue->CommentAddress,
+ RT->Config->Get('CorrespondAddress'),
+ RT->Config->Get('CommentAddress'),
+ ) {
+ my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' );
+ next if $res{'exit_code'} || !$res{'info'};
+ %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address );
+ next if $res{'exit_code'} || !$res{'info'};
+ $encrypt_for = $address;
+ }
+ unless ( $encrypt_for ) {
+ return (0, $self->loc('No key suitable for encryption'));
+ }
+
+ $self->__Set( Field => 'ContentType', Value => $type );
+ $self->SetHeader( 'Content-Type' => $type );
+
+ my $content = $self->Content;
+ my %res = RT::Crypt::GnuPG::SignEncryptContent(
+ Content => \$content,
+ Sign => 0,
+ Encrypt => 1,
+ Recipients => [ $encrypt_for ],
+ );
+ if ( $res{'exit_code'} ) {
+ return (0, $self->loc('GnuPG error. Contact with administrator'));
+ }
+
+ my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
+ unless ( $status ) {
+ return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
+ }
+ return (1, $self->loc('Successfuly encrypted data'));
+}
+
+sub Decrypt {
+ my $self = shift;
+
+ my $txn = $self->TransactionObj;
+ return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
+ return (0, $self->loc('Permission Denied'))
+ unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
+ return (0, $self->loc('GnuPG integration is disabled'))
+ unless RT->Config->Get('GnuPG')->{'Enable'};
+
+ require RT::Crypt::GnuPG;
+
+ my $type = $self->ContentType;
+ if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+ ($type) = ($type =~ /original-type="(.*)"/i);
+ $type ||= 'application/octeat-stream';
+ } else {
+ return (1, $self->loc('Is not encrypted'));
+ }
+ $self->__Set( Field => 'ContentType', Value => $type );
+ $self->SetHeader( 'Content-Type' => $type );
+
+ my $content = $self->Content;
+ my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, );
+ if ( $res{'exit_code'} ) {
+ return (0, $self->loc('GnuPG error. Contact with administrator'));
+ }
+
+ my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
+ unless ( $status ) {
+ return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
+ }
+ return (1, $self->loc('Successfuly decrypted data'));
+}
+
=head2 _Value
Takes the name of a table column.
Modified: rt/branches/3.7-EXPERIMENTAL/lib/RT/Crypt/GnuPG.pm
==============================================================================
--- rt/branches/3.7-EXPERIMENTAL/lib/RT/Crypt/GnuPG.pm (original)
+++ rt/branches/3.7-EXPERIMENTAL/lib/RT/Crypt/GnuPG.pm Sat Jan 26 20:01:30 2008
@@ -135,6 +135,13 @@
=back
+=head3 Encrypting data in the database
+
+You can allow users to encrypt data in the database using
+option C<AllowEncryptDataInDB>. By default it's disabled.
+Users must have rights to see and modify tickets to use
+this feature.
+
=head2 %GnuPGOptions
Use this hash to set options of the 'gnupg' program. You can define almost any
@@ -800,6 +807,103 @@
return %res;
}
+sub SignEncryptContent {
+ my %args = (
+ Content => undef,
+
+ Sign => 1,
+ Signer => undef,
+ Passphrase => undef,
+
+ Encrypt => 1,
+ Recipients => undef,
+
+ @_
+ );
+ return unless $args{'Sign'} || $args{'Encrypt'};
+
+ my $gnupg = new GnuPG::Interface;
+ my %opt = RT->Config->Get('GnuPGOptions');
+ $opt{'digest-algo'} ||= 'SHA1';
+ $opt{'default_key'} = $args{'Signer'}
+ if $args{'Sign'} && $args{'Signer'};
+ $gnupg->options->hash_init(
+ _PrepareGnuPGOptions( %opt ),
+ armor => 1,
+ meta_interactive => 0,
+ );
+
+ # handling passphrase in GnupGOptions
+ $args{'Passphrase'} = delete $opt{'passphrase'}
+ if !defined($args{'Passphrase'});
+
+ if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
+ $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+ }
+
+ if ( $args{'Encrypt'} ) {
+ $gnupg->options->push_recipients( $_ ) foreach
+ map UseKeyForEncryption($_) || $_,
+ @{ $args{'Recipients'} || [] };
+ }
+
+ my %res;
+
+ my ($tmp_fh, $tmp_fn) = File::Temp::tempfile();
+ binmode $tmp_fh, ':raw';
+
+ my %handle;
+ my $handles = GnuPG::Handles->new(
+ stdin => ($handle{'input'} = new IO::Handle),
+ stdout => $tmp_fh,
+ stderr => ($handle{'error'} = new IO::Handle),
+ logger => ($handle{'logger'} = new IO::Handle),
+ status => ($handle{'status'} = new IO::Handle),
+ );
+ $handles->options( 'stdout' )->{'direct'} = 1;
+ $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
+
+ eval {
+ local $SIG{'CHLD'} = 'DEFAULT';
+ my $method = $args{'Sign'} && $args{'Encrypt'}
+ ? 'sign_and_encrypt'
+ : ($args{'Sign'}? 'clearsign': 'encrypt');
+ my $pid = _safe_run_child { $gnupg->$method( handles => $handles ) };
+ $handle{'input'}->print( ${ $args{'Content'} } );
+ close $handle{'input'};
+ waitpid $pid, 0;
+ };
+ $res{'exit_code'} = $?;
+ my $err = $@;
+
+ foreach ( qw(error logger status) ) {
+ $res{$_} = do { local $/; readline $handle{$_} };
+ delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+ close $handle{$_};
+ }
+ $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+ $RT::Logger->warning( $res{'error'} ) if $res{'error'};
+ $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+ if ( $err || $res{'exit_code'} ) {
+ $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+ return %res;
+ }
+
+ ${ $args{'Content'} } = '';
+ seek $tmp_fh, 0, 0;
+ while (1) {
+ my $status = read $tmp_fh, my $buf, 4*1024;
+ unless ( defined $status ) {
+ $RT::Logger->crit( "couldn't read message: $!" );
+ } elsif ( !$status ) {
+ last;
+ }
+ ${ $args{'Content'} } .= $buf;
+ }
+
+ return %res;
+}
+
sub FindProtectedParts {
my %args = ( Entity => undef, CheckBody => 1, @_ );
my $entity = $args{'Entity'};
@@ -1245,6 +1349,86 @@
return %res;
}
+sub DecryptContent {
+ my %args = (
+ Content => undef,
+ Passphrase => undef,
+ @_
+ );
+
+ my $gnupg = new GnuPG::Interface;
+ my %opt = RT->Config->Get('GnuPGOptions');
+ $opt{'digest-algo'} ||= 'SHA1';
+ $gnupg->options->hash_init(
+ _PrepareGnuPGOptions( %opt ),
+ meta_interactive => 0,
+ );
+
+ # handling passphrase in GnupGOptions
+ $args{'Passphrase'} = delete $opt{'passphrase'}
+ if !defined($args{'Passphrase'});
+
+ $args{'Passphrase'} = GetPassphrase()
+ unless defined $args{'Passphrase'};
+
+ my ($tmp_fh, $tmp_fn) = File::Temp::tempfile();
+ binmode $tmp_fh, ':raw';
+
+ my %handle;
+ my $handles = GnuPG::Handles->new(
+ stdin => ($handle{'input'} = new IO::Handle),
+ stdout => $tmp_fh,
+ stderr => ($handle{'error'} = new IO::Handle),
+ logger => ($handle{'logger'} = new IO::Handle),
+ status => ($handle{'status'} = new IO::Handle),
+ );
+ $handles->options( 'stdout' )->{'direct'} = 1;
+
+ my %res;
+ eval {
+ local $SIG{'CHLD'} = 'DEFAULT';
+ $gnupg->passphrase( $args{'Passphrase'} );
+ my $pid = _safe_run_child { $gnupg->decrypt( handles => $handles ) };
+ print { $handle{'input'} } ${ $args{'Content'} };
+ close $handle{'input'};
+
+ waitpid $pid, 0;
+ };
+ $res{'exit_code'} = $?;
+ foreach ( qw(error logger status) ) {
+ $res{$_} = do { local $/; readline $handle{$_} };
+ delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+ close $handle{$_};
+ }
+ $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+ $RT::Logger->warning( $res{'error'} ) if $res{'error'};
+ $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+
+ # if the decryption is fine but the signature is bad, then without this
+ # status check we lose the decrypted text
+ # XXX: add argument to the function to control this check
+ if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
+ if ( $@ || $? ) {
+ $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+ return %res;
+ }
+ }
+
+ ${ $args{'Content'} } = '';
+ seek $tmp_fh, 0, 0;
+ while (1) {
+ my $status = read $tmp_fh, my $buf, 4*1024;
+ unless ( defined $status ) {
+ $RT::Logger->crit( "couldn't read message: $!" );
+ } elsif ( !$status ) {
+ last;
+ }
+ ${ $args{'Content'} } .= $buf;
+ }
+
+ return %res;
+}
+
=head2 GetPassphrase [ Address => undef ]
Returns passphrase, called whenever it's required with Address as a named argument.
More information about the Rt-commit
mailing list