summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2017-09-27 16:29:50 +0200
committerSergey Poznyakoff <gray@gnu.org>2017-09-27 16:29:50 +0200
commitee07987c9da7a4d349b92df8025665ce140b7cba (patch)
tree05054fc05f3ae5cb81bcc0469de009e6ce0791fc
parent9b6e0bcb9b86c14456e6fabeac4e396086bf8485 (diff)
downloadsourceyard-ee07987c9da7a4d349b92df8025665ce140b7cba.tar.gz
sourceyard-ee07987c9da7a4d349b92df8025665ce140b7cba.tar.bz2
Implement editing the SSH and GPG keys
-rw-r--r--lib/App/Sourceyard/GPG/Key.pm116
-rw-r--r--lib/App/Sourceyard/GPG/PublicKey.pm97
-rw-r--r--lib/Sourceyard.pm5
-rw-r--r--lib/Sourceyard/Controller/User.pm77
-rw-r--r--lib/Sourceyard/Schema/Result/GPG_Keys.pm13
-rw-r--r--lib/Sourceyard/User.pm31
-rw-r--r--public/css/internal/base.css21
-rw-r--r--public/images/.gitignore3
-rw-r--r--public/images/common/bool1/ok.orig.png (renamed from public/images/common/bool1/ok.png)bin2135 -> 2135 bytes
-rw-r--r--public/images/common/bool1/wrong.orig.png (renamed from public/images/common/bool1/wrong.png)bin2512 -> 2512 bytes
-rw-r--r--templates/user/admin.html.ep27
-rw-r--r--templates/user/gpg_keys.html.ep53
-rw-r--r--templates/user/ssh_keys.html.ep38
13 files changed, 460 insertions, 21 deletions
diff --git a/lib/App/Sourceyard/GPG/Key.pm b/lib/App/Sourceyard/GPG/Key.pm
new file mode 100644
index 0000000..7bfb337
--- /dev/null
+++ b/lib/App/Sourceyard/GPG/Key.pm
@@ -0,0 +1,116 @@
+package App::Sourceyard::GPG::Key;
+
+use strict;
+use warnings;
+use Carp;
+use parent 'Exporter';
+use DateTime;
+
+my @ATTRIBUTES = qw(type
+ validity
+ length
+ algo
+ keyid
+ creation_date
+ expiry_date
+ serial
+ ownertrust
+ uid
+ sigclass
+ capa);
+
+my %ATTRIBTYPE = (uid => 'ARRAY');
+
+{
+ no strict 'refs';
+ foreach my $attribute (@ATTRIBUTES) {
+ if ($ATTRIBTYPE{$attribute} && $ATTRIBTYPE{$attribute} eq 'ARRAY') {
+ *{ __PACKAGE__ . '::' . $attribute } = sub {
+ my $self = shift;
+ @{$self->{$attribute}} = @_ if @_;
+ return @{$self->{$attribute}};
+ };
+ } else {
+ *{ __PACKAGE__ . '::' . $attribute } = sub {
+ my $self = shift;
+ $self->{$attribute} = shift if @_;
+ return $self->{$attribute};
+ }
+ };
+ }
+}
+
+sub algoname {
+ my ($self) = @_;
+ my %trans = (
+ 1 => 'RSA',
+ 16 => 'Elgamal (encrypt only)',
+ 17 => 'DSA (sign only)',
+ 20 => 'Elgamal');
+ my $c = $self->algo;
+ return $trans{$c} || $c;
+}
+
+sub _convdate {
+ my $datestr = shift;
+
+ return undef unless defined $datestr;
+
+ if ($datestr =~ /^(?<Y>\d{4})
+ (?<m>\d{2})
+ (?<d>\d{2})
+ T
+ (?<H>\d{2})
+ (?<M>\d{2})
+ (?<S>\d{2})$/x) {
+ return new DateTime(year => $+{Y},
+ month => $+{m},
+ day => $+{d},
+ hour => $+{H},
+ minute => $+{M},
+ second => $+{S});
+ } else {
+ return DateTime->from_epoch(epoch => $datestr);
+ }
+}
+
+sub new {
+ my $class = shift;
+ my %info = ();
+ @info{@ATTRIBUTES} = map { defined($_) && $_ eq '' ? undef : $_ } @_;
+ $info{uid} = [ $info{uid} ] if $info{uid};
+ $info{creation_date} = _convdate($info{creation_date});
+ $info{expiry_date} = _convdate($info{expiry_date});
+ return bless \%info, $class;
+}
+
+sub fingerprint {
+ my ($self, $fpr) = @_;
+ if ($fpr) {
+ $self->{fingerprint} = $fpr;
+ }
+ return $self->{fingerprint};
+}
+
+sub addsubkey {
+ my ($self, $sk) = @_;
+ push @{$self->{subkeys}}, $sk;
+}
+
+sub subkey {
+ my ($self, $n) = @_;
+ $n ||= 0;
+ if ($n < 0) {
+ $n += @{$self->{subkeys}};
+ return undef if $n < 0;
+ }
+ return undef if $n > $#{$self->{subkeys}};
+ return $self->{subkeys}[$n];
+}
+
+sub adduid {
+ my ($self, $id) = @_;
+ push @{$self->{uid}}, $id;
+}
+
+1;
diff --git a/lib/App/Sourceyard/GPG/PublicKey.pm b/lib/App/Sourceyard/GPG/PublicKey.pm
new file mode 100644
index 0000000..3e3ef2d
--- /dev/null
+++ b/lib/App/Sourceyard/GPG/PublicKey.pm
@@ -0,0 +1,97 @@
+package App::Sourceyard::GPG::PublicKey;
+use strict;
+use warnings;
+use Carp;
+use parent 'Exporter';
+use POSIX::Run::Capture;
+use App::Sourceyard::GPG::Key;
+
+our $gpgbin = 'gpg';
+
+sub new {
+ my ($class, $key) = @_;
+ my $self = bless {}, $class;
+ if ($key) {
+ $self->blob($key);
+ }
+ return $self;
+}
+
+sub _parse {
+ my ($self, $blob) = @_;
+ my $out;
+ my $cap = new POSIX::Run::Capture(
+ argv => [ $gpgbin,
+ '--with-colons',
+ '--with-fingerprint',
+ '--with-fingerprint',
+ '--fixed-list-mode' ],
+ stdin => $blob,
+ # FIXME: stderr => log
+ timeout => 5
+ );
+ unless ($cap->run && $cap->status == 0) {
+ return undef;
+ }
+
+ while ($_ = $cap->next_line(1)) {
+ chomp;
+ my @fields = split /:/;
+ my $type = shift @fields;
+ if ($type eq 'pub') {
+ push @{$self->{_pubkeys}},
+ new App::Sourceyard::GPG::Key($type, @fields);
+ } elsif ($type eq 'sub') {
+ $self->pubkey(-1)->addsubkey(
+ new App::Sourceyard::GPG::Key($type, @fields)
+ );
+ } elsif ($type eq 'fpr') {
+ if ($self->pubkey(-1)->subkey) {
+ $self->pubkey(-1)->subkey(-1)->fingerprint($fields[8]);
+ } else {
+ $self->pubkey(-1)->fingerprint($fields[8]);
+ }
+ } elsif ($type eq 'uid') {
+ if ($self->pubkey(-1)->subkey) {
+ $self->pubkey(-1)->subkey(-1)->adduid($fields[8]);
+ } else {
+ $self->pubkey(-1)->adduid($fields[8]);
+ }
+ }
+ }
+
+ return @{$self->{_pubkeys}};
+}
+
+sub blob {
+ my ($self, $newblob) = @_;
+ if ($newblob) {
+ delete $self->{_pubkey};
+ if ($self->_parse($newblob)) {
+ $self->{_blob} = $newblob;
+ } else {
+ croak "invalid key";
+ }
+ }
+ return $self->{_blob};
+}
+
+sub pubkey {
+ my ($self, $n) = @_;
+ $n ||= 0;
+ if ($n < 0) {
+ $n += @{$self->{_pubkeys}};
+ return undef if $n < 0;
+ }
+ return undef if $n > $#{$self->{_pubkeys}};
+
+ return $self->{_pubkeys}[$n];
+}
+
+sub pubkeys {
+ my ($self) = @_;
+ return () unless exists $self->{_pubkeys};
+ return @{$self->{_pubkeys}};
+}
+
+1;
diff --git a/lib/Sourceyard.pm b/lib/Sourceyard.pm
index 34d67c8..74a1c0e 100644
--- a/lib/Sourceyard.pm
+++ b/lib/Sourceyard.pm
@@ -61,8 +61,9 @@ sub startup {
}
return 1;
});
- $auth->get(':action')->to(controller => 'user');
- $auth->post(':action')->to(controller => 'user');
+ $auth->any(['GET', 'POST'] => '/:action' =>
+ [action => [ qw(admin ssh_keys gpg_keys) ]])->to(controller => 'user');
+# $auth->post(':action')->to(controller => 'user');
}
sub _top_menu_item {
diff --git a/lib/Sourceyard/Controller/User.pm b/lib/Sourceyard/Controller/User.pm
index 0ec562d..7f0ba19 100644
--- a/lib/Sourceyard/Controller/User.pm
+++ b/lib/Sourceyard/Controller/User.pm
@@ -81,4 +81,81 @@ sub admin {
$self->render;
}
+sub ssh_keys {
+ my $self = shift;
+ if ($self->req->method eq 'POST') {
+ my $user = $self->stash('user');
+ $self->{_sy_update} = 0;
+ my $i;
+ foreach my $k ($user->authorized_keys->all) {
+ $i++;
+ if ($self->param("del_$i")) {
+ $self->db->resultset('Authorized_Keys')
+ ->find({ key_id => $k->key_id })->delete;
+ $self->{_sy_update} = 1;
+ last;
+ }
+ my $s = $self->param("key_$i"); # FIXME: Normalize
+ if ($s ne $k->ssh_key) {
+ if ($s = $user->validate_ssh_key($s)) {
+ $self->db->resultset('Authorized_Keys')
+ ->find({ key_id => $k->key_id })->ssh_key($s);
+ $self->{_sy_update} = 1;
+ } else {
+ $self->stash('error_msg', "Invalid SSH key #$i");
+ }
+ last;
+ }
+ }
+
+ if (my $s = $self->param("key_new")) {
+ $i++;
+ if ($s = $user->validate_ssh_key($s)) {
+ $self->db->resultset('Authorized_Keys')
+ ->create({
+ user_id => $user->user_id,
+ ssh_key => $s
+ });
+ $self->{_sy_update} = 1;
+ $self->param(key_new => undef);
+ } else {
+ $self->stash('error_msg', "Invalid SSH key #$i");
+ }
+ }
+ }
+ $self->render;
+}
+
+sub gpg_keys {
+ my $self = shift;
+ if ($self->req->method eq 'POST') {
+ my $user = $self->stash('user');
+ $self->{_sy_update} = 0;
+ my $i;
+ foreach my $k ($user->gpg_keys->all) {
+ $i++;
+ if ($self->param("del_$i")) {
+ $self->db->resultset('GPG_Keys')
+ ->find({ key_id => $k->key_id })->delete;
+ $self->{_sy_update} = 1;
+ last;
+ }
+ }
+ if (my $s = $self->param("key_new")) {
+ if ($s = $user->validate_gpg_key($s)) {
+ $self->db->resultset('GPG_Keys')
+ ->create({
+ user_id => $user->user_id,
+ gpg_key => $s->blob
+ });
+ $self->{_sy_update} = 1;
+ } else {
+ $self->stash('error_msg', "Invalid GPG key");
+ }
+ $self->param(key_new => undef);
+ }
+ }
+ $self->render;
+}
+
1;
diff --git a/lib/Sourceyard/Schema/Result/GPG_Keys.pm b/lib/Sourceyard/Schema/Result/GPG_Keys.pm
index 9416b31..8699040 100644
--- a/lib/Sourceyard/Schema/Result/GPG_Keys.pm
+++ b/lib/Sourceyard/Schema/Result/GPG_Keys.pm
@@ -4,6 +4,7 @@ use strict;
use warnings;
use base 'DBIx::Class::Core';
+use App::Sourceyard::GPG::PublicKey;
__PACKAGE__->table('gpg_keys');
__PACKAGE__->add_columns(
@@ -21,4 +22,16 @@ __PACKAGE__->add_columns(
__PACKAGE__->set_primary_key('key_id');
__PACKAGE__->belongs_to('user_id' => 'Sourceyard::Schema::Result::User');
+__PACKAGE__->inflate_column('gpg_key', {
+ inflate => sub {
+ my ($value, $object) = @_;
+ return new App::Sourceyard::GPG::PublicKey($value);
+ },
+ deflate => sub {
+ my ($value, $object) = @_;
+ return $value->blob;
+ }
+});
+
+
1;
diff --git a/lib/Sourceyard/User.pm b/lib/Sourceyard/User.pm
index f5ca31e..cc3524e 100644
--- a/lib/Sourceyard/User.pm
+++ b/lib/Sourceyard/User.pm
@@ -6,6 +6,7 @@ use Email::Address::XS;
use Net::DNS;
use Crypt::Cracklib;
use App::Sourceyard::Password;
+use Net::SSH::Perl::Key;
use parent qw(Sourceyard::Schema::Result::User);
#use parent qw(DBIx::Class::ResultSet);
@@ -65,4 +66,34 @@ sub validate_password {
return new App::Sourceyard::Password($pass);
}
+sub validate_ssh_key {
+ my ($self, $key) = @_;
+
+ $key =~ s/\n//;
+ $key =~ s/^\s+//;
+ $key =~ s/\s+$//;
+
+ my @in = split /\s+/, $key;
+ my $type = shift @in;
+ my @block;
+ while (my $frag = shift @in) {
+ if ($frag =~ m{^[A-Za-z0-9+/=]+$}) {
+ push @block, $frag;
+ $key = $type . ' ' . join('', @block) . ' ' . join(' ', @in);
+ return $key if
+ eval { Net::SSH::Perl::Key->extract_public(undef, $key) };
+ }
+ }
+
+ return undef;
+}
+
+sub validate_gpg_key {
+ my ($self, $key) = @_;
+ my $pk;
+ eval {
+ $pk = new App::Sourceyard::GPG::PublicKey($key);
+ };
+ return $pk;
+}
1;
diff --git a/public/css/internal/base.css b/public/css/internal/base.css
index 27e714c..906ad78 100644
--- a/public/css/internal/base.css
+++ b/public/css/internal/base.css
@@ -886,7 +886,14 @@ div.input input {
div.input select {
display: table-cell;
}
-
+div.input textarea {
+ display: table-cell;
+}
+
+*.cell {
+ display: table-cell;
+}
+
button {
border: 1px solid gray;
background-color: white;
@@ -904,3 +911,15 @@ button.cancel {
color: white;
}
+table.input {
+ border-collapse: collapse;
+}
+table.input td {
+ padding-right: 2em;
+}
+table.input thead td {
+ font-weight: bold;
+ text-align: center;
+ border-bottom: 1px solid gray;
+}
+ \ No newline at end of file
diff --git a/public/images/.gitignore b/public/images/.gitignore
new file mode 100644
index 0000000..2bdfe67
--- /dev/null
+++ b/public/images/.gitignore
@@ -0,0 +1,3 @@
+stamp-icons
+*.png
+! *.orig.png
diff --git a/public/images/common/bool1/ok.png b/public/images/common/bool1/ok.orig.png
index ca7034e..ca7034e 100644
--- a/public/images/common/bool1/ok.png
+++ b/public/images/common/bool1/ok.orig.png
Binary files differ
diff --git a/public/images/common/bool1/wrong.png b/public/images/common/bool1/wrong.orig.png
index bca36a5..bca36a5 100644
--- a/public/images/common/bool1/wrong.png
+++ b/public/images/common/bool1/wrong.orig.png
Binary files differ
diff --git a/templates/user/admin.html.ep b/templates/user/admin.html.ep
index 8f5f6f7..0f9103b 100644
--- a/templates/user/admin.html.ep
+++ b/templates/user/admin.html.ep
@@ -28,25 +28,16 @@
</p>
% end
%= alt_tag div => (class => 'boxitem') => begin
- <a href="editsshkeys.php">
- Register an SSH Public Key
+ <a href="ssh_keys">
+ Register SSH Public Keys
</a>
-% foreach my $k ($user->authorized_keys->all) {
- <p class="smaller">
- <div style="width: 20em; height: 2em; overflow-x: scroll;
- overflow-y: hidden;">
- <%= $k->ssh_key %>
- </div>
-%# %= text_field k => $k->ssh_key, size => 40, disabled => 'disabled'
-%# %= substr($k->ssh_key ,0, 10)
- </p>
-% end
-%# Usually, SSH Keys can be used to get secure shell access, or to
-%# run rsync or cvs commands.
-%# </p>
-% end
+ <p class="smaller">
+ Usually, SSH Keys can be used to get secure shell access, or to
+ run rsync or cvs commands.
+ </p>
+% end
%= alt_tag div => (class => 'boxitem') => begin
- <a href="change.php?item=gpgkey">
+ <a href="gpg_keys">
Edit GPG Key
</a>
<p class="smaller">
@@ -108,7 +99,7 @@
%= opt_input 'mail', 'email' => $user->email
% end
%= alt_tag div => (class => 'boxitem') => begin
- <a href="change_notifications">
+ <a href="notifications">
Edit Personal Notification Settings
</a>
<p class="smaller">
diff --git a/templates/user/gpg_keys.html.ep b/templates/user/gpg_keys.html.ep
new file mode 100644
index 0000000..0b01a16
--- /dev/null
+++ b/templates/user/gpg_keys.html.ep
@@ -0,0 +1,53 @@
+% layout 'user';
+% title 'My GPG keys';
+%= include "include/feedback"
+<h3>Registered GPG keys</h3>
+<div class="form">
+%= form_for 'gpg_keys' => (method => 'POST') => begin
+% my $i = 0;
+% foreach my $k ($user->gpg_keys->all) {
+% $i++;
+ <table class="input">
+ <thead>
+ <tr>
+ <td>Key ID</td>
+ <td>Fingerprint</td>
+ <td>User ID</td>
+ <td>Lenght & algorithm</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <%= $k->gpg_key->pubkey->keyid %>
+ </td>
+ <td>
+ <%= $k->gpg_key->pubkey->fingerprint %>
+ </td>
+ <td>
+ <%= ${[$k->gpg_key->pubkey->uid]}[0] %>
+ </td>
+ <td>
+ <span class="smaller">
+ (<%= $k->gpg_key->pubkey->length %> bits,
+ <%= $k->gpg_key->pubkey->algoname %>)</span>
+ </td>
+ <td>
+%# %= text_field "key_$i" => $k->gpg_key->blob, size => '100'
+%= tag 'button' => (class => 'cancel', name => "del_$i", value => 1) => begin
+ x
+% end
+ </td>
+ </tbody>
+ </table>
+% }
+ <h3>Add new key</h3>
+ <div class="input">
+%= text_area "key_new", cols => 70, rows => 10, wrap => 'virtual'
+ <div class="center">
+ %= submit_button 'Add'
+ </div>
+ </div>
+% end
+</div>
+
diff --git a/templates/user/ssh_keys.html.ep b/templates/user/ssh_keys.html.ep
new file mode 100644
index 0000000..46c2252
--- /dev/null
+++ b/templates/user/ssh_keys.html.ep
@@ -0,0 +1,38 @@
+% layout 'user';
+% title 'My SSH keys';
+%= include "include/feedback"
+<div class="form">
+%= form_for 'ssh_keys' => (method => 'POST') => begin
+% use Net::SSH::Perl::Key;
+% my $i = 0;
+% foreach my $k ($user->authorized_keys->all) {
+% $i++;
+% my ($size, $fingerprint);
+% if (my $x = eval { Net::SSH::Perl::Key->extract_public(undef,$k->ssh_key) }) {
+% $size = $x->size;
+% $fingerprint = $x->fingerprint('md5');
+% }
+<div class="input">
+%= label_for "key_$i" => "Key #$i:", class => 'preinput'
+%# %= text_area "key_$i" => $k->ssh_key, cols => 80, rows => 10, wrap => 'hard'
+%= text_field "key_$i" => $k->ssh_key, size => '100'
+%= tag 'button' => (class => 'cancel', name => "del_$i", value => 1) => begin
+ x
+% end
+</div>
+<div class="input">
+ %= label_for 'a' => 'size fingerprint', class => 'preinput smaller'
+ <span class="cell smaller"><%= $size %> <%= $fingerprint %></span>
+</div>
+% }
+<div class="input">
+% $i++;
+%= label_for "key_new" => "Key #$i (new):", class => 'preinput'
+%= text_field "key_new" => '', size => '100'
+%# %= file_field "file_$i"
+%= tag 'button' => (class => 'update', name => 'key_new_add', value => 1) => begin
+ +
+% end
+</div>
+
+% end

Return to:

Send suggestions and report system problems to the System administrator.