add key delete feature and update authorized_key file...
...system
... | ... |
@@ -25,6 +25,10 @@ |
25 | 25 |
;;; encoding name follow Perl encoding API. |
26 | 26 |
;encoding_suspects=cp932,UTF-8 |
27 | 27 |
|
28 |
+;;; authorized_keys file for public key authentication via ssh. |
|
29 |
+;;; default is "$ENV{HOME}/.ssh/authorized_keys" |
|
30 |
+; authorized_keys_file=/home/gitprep/.ssh/authorized_keys |
|
31 |
+ |
|
28 | 32 |
[admin] |
29 | 33 |
;;; If you forget admin password, |
30 | 34 |
;;; set this value to 1 and access /reset-password page. |
... | ... |
@@ -68,6 +68,20 @@ sub startup { |
68 | 68 |
weaken $manager->{app}; |
69 | 69 |
$self->manager($manager); |
70 | 70 |
|
71 |
+ # authorized_keys file |
|
72 |
+ my $authorized_keys_file = $self->config('authorized_keys_file'); |
|
73 |
+ unless (defined $authorized_keys_file) { |
|
74 |
+ if (defined $ENV{HOME}) { |
|
75 |
+ $authorized_keys_file = "$ENV{HOME}/.ssh/authorized_keys_file"; |
|
76 |
+ } |
|
77 |
+ } |
|
78 |
+ if (defined $authorized_keys_file) { |
|
79 |
+ $self->manager->authorized_keys_file($authorized_keys_file); |
|
80 |
+ } |
|
81 |
+ else { |
|
82 |
+ $self->app->log->warn(qq/Config "authorized_keys_file" can't be detected/); |
|
83 |
+ } |
|
84 |
+ |
|
71 | 85 |
# Repository home |
72 | 86 |
my $rep_home = $ENV{GITPREP_REP_HOME} || $self->home->rel_file('data/rep'); |
73 | 87 |
$git->rep_home($rep_home); |
... | ... |
@@ -6,8 +6,12 @@ use Encode 'encode'; |
6 | 6 |
use File::Copy qw/move copy/; |
7 | 7 |
use File::Path qw/mkpath rmtree/; |
8 | 8 |
use File::Temp (); |
9 |
+use Fcntl ':flock'; |
|
10 |
+use Carp 'croak'; |
|
11 |
+use File::Copy qw/copy move/; |
|
9 | 12 |
|
10 | 13 |
has 'app'; |
14 |
+has 'authorized_keys_file'; |
|
11 | 15 |
|
12 | 16 |
sub admin_user { |
13 | 17 |
my $self = shift; |
... | ... |
@@ -448,6 +452,121 @@ EOS |
448 | 452 |
} |
449 | 453 |
} |
450 | 454 |
|
455 |
+ |
|
456 |
+sub update_authorized_keys_file { |
|
457 |
+ my $self = shift; |
|
458 |
+ |
|
459 |
+ my $authorized_keys_file = $self->authorized_keys_file; |
|
460 |
+ if (defined $authorized_keys_file) { |
|
461 |
+ |
|
462 |
+ # Lock file |
|
463 |
+ my $lock_file = $self->app->rel_file('lock/authorized_keys'); |
|
464 |
+ open my $lock_fh, $lock_file |
|
465 |
+ or croak "Can't open lock file $lock_file"; |
|
466 |
+ flock $lock_fh, LOCK_EX |
|
467 |
+ or croak "Can't lock $lock_file"; |
|
468 |
+ |
|
469 |
+ # Create authorized_keys_file |
|
470 |
+ unless (-f $authorized_keys_file) { |
|
471 |
+ open my $fh, '>', $authorized_keys_file |
|
472 |
+ or croak "Can't create $authorized_keys_file"; |
|
473 |
+ } |
|
474 |
+ |
|
475 |
+ # Parse file |
|
476 |
+ my ($before_part, $gitprep_part, $after_part) |
|
477 |
+ = $self->_parse_authorized_keys_file($authorized_keys_file); |
|
478 |
+ |
|
479 |
+ # Backup at first time |
|
480 |
+ if ($gitprep_part eq '') { |
|
481 |
+ # Backup original file |
|
482 |
+ my $to = "$authorized_keys_file.gitprep.original"; |
|
483 |
+ unless (-f $to) { |
|
484 |
+ copy $authorized_keys_file, $to |
|
485 |
+ or croak "Can't copy $authorized_keys_file to $to"; |
|
486 |
+ } |
|
487 |
+ } |
|
488 |
+ |
|
489 |
+ # Create public keys |
|
490 |
+ my $ssh_public_keys = $self->app->dbi->mode('ssh_public_key')->select->all; |
|
491 |
+ my $ssh_public_keys_str = ''; |
|
492 |
+ for my $key (@$ssh_public_keys) { |
|
493 |
+ my $ssh_public_key_str = $self->app->home->rel_file('script/gitprep-shell') |
|
494 |
+ . " $key->{user_id},no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $key->{key}"; |
|
495 |
+ $ssh_public_keys_str .= "$ssh_public_key_str\n\n"; |
|
496 |
+ } |
|
497 |
+ |
|
498 |
+ # Output tmp file |
|
499 |
+ my $output = "$before_part\n\n$ssh_public_keys_str\n\n$after_part"; |
|
500 |
+ my $output_file = "$authorized_keys_file.gitprep.tmp"; |
|
501 |
+ open my $out_fh, '>', $output_file |
|
502 |
+ or croak "Can't create authorized_keys tmp file $output_file"; |
|
503 |
+ print $out_fh $output; |
|
504 |
+ close $out_fh |
|
505 |
+ or croak "Can't close authorized_keys tmp file $output_file"; |
|
506 |
+ |
|
507 |
+ # Replace |
|
508 |
+ move $output_file, $authorized_keys_file |
|
509 |
+ or croak "Can't replace $authorized_keys_file by $output_file"; |
|
510 |
+ |
|
511 |
+ # Unlock file |
|
512 |
+ flock $lock_fh, LOCK_EX |
|
513 |
+ or croak "Can't unlock $lock_file" |
|
514 |
+ } |
|
515 |
+ else { |
|
516 |
+ croak qq/authorized_keys file "$authorized_keys_file" is not found./; |
|
517 |
+ } |
|
518 |
+} |
|
519 |
+ |
|
520 |
+sub _parse_authorized_keys_file { |
|
521 |
+ my ($self, $file) = shift; |
|
522 |
+ |
|
523 |
+ my $start_symbol = "# gitprep start"; |
|
524 |
+ my $end_symbol = "# gitprep end"; |
|
525 |
+ |
|
526 |
+ # Parse |
|
527 |
+ open my $fh, '<', $file |
|
528 |
+ or croak "Can't open $file"; |
|
529 |
+ my $start_symbol_count = 0; |
|
530 |
+ my $end_symbol_count = 0; |
|
531 |
+ my $before_part = ''; |
|
532 |
+ my $gitprep_part = ''; |
|
533 |
+ my $after_part = ''; |
|
534 |
+ my $error_prefix = "authorized_keys file $file format error:"; |
|
535 |
+ while (my $line = <$fh>) { |
|
536 |
+ if ($line =~ /^$start_symbol/) { |
|
537 |
+ if ($start_symbol_count > 0) { |
|
538 |
+ croak qq/$error_prefix "$start_symbol" is found more than one/; |
|
539 |
+ } |
|
540 |
+ else { |
|
541 |
+ if ($end_symbol_count > 0) { |
|
542 |
+ croak qq/$error_prefix "$end_symbol" is found before "$start_symbol"/; |
|
543 |
+ } |
|
544 |
+ else { |
|
545 |
+ $start_symbol_count++; |
|
546 |
+ } |
|
547 |
+ } |
|
548 |
+ } |
|
549 |
+ elsif ($line =~ /^$end_symbol/) { |
|
550 |
+ if ($end_symbol > 0) { |
|
551 |
+ croak qq/$error_prefix "$end_symbol" is found more than one/; |
|
552 |
+ } |
|
553 |
+ else { |
|
554 |
+ $end_symbol++; |
|
555 |
+ } |
|
556 |
+ } |
|
557 |
+ elsif ($start_symbol_count == 0 && $end_symbol_count == 0) { |
|
558 |
+ $before_part .= $line; |
|
559 |
+ } |
|
560 |
+ elsif ($start_symbol_count == 1 && $end_symbol_count == 0) { |
|
561 |
+ $gitprep_part .= $line; |
|
562 |
+ } |
|
563 |
+ elsif ($start_symbol_count == 1 && $end_symbol_count == 1) { |
|
564 |
+ $after_part .= $line; |
|
565 |
+ } |
|
566 |
+ } |
|
567 |
+ return ($before_part, $gitprep_part, $after_part); |
|
568 |
+} |
|
569 |
+ |
|
451 | 570 |
sub _create_project { |
452 | 571 |
my ($self, $user, $project, $params) = @_; |
453 | 572 |
$params ||= {}; |
... | ... |
@@ -85,7 +85,10 @@ |
85 | 85 |
key => $key |
86 | 86 |
}; |
87 | 87 |
eval { |
88 |
- app->dbi->model('ssh_public_key')->insert($p); |
|
88 |
+ app->dbi->connector->txn(sub { |
|
89 |
+ app->dbi->model('ssh_public_key')->insert($p); |
|
90 |
+ # $self->manager->update_authorized_keys_file; |
|
91 |
+ }); |
|
89 | 92 |
}; |
90 | 93 |
|
91 | 94 |
if (my $e = $@) { |
... | ... |
@@ -102,6 +105,22 @@ |
102 | 105 |
$errors = $vresult->messages; |
103 | 106 |
} |
104 | 107 |
} |
108 |
+ # Delete ssh public key |
|
109 |
+ elsif ($op eq 'delete') { |
|
110 |
+ my $row_id = param('row-id'); |
|
111 |
+ eval { |
|
112 |
+ app->dbi->model('ssh_public_key')->delete(where => {row_id => $row_id}); |
|
113 |
+ }; |
|
114 |
+ |
|
115 |
+ if (my $e = $@) { |
|
116 |
+ app->log->error(url_with . ": $e"); |
|
117 |
+ $errors = ['Internal Error']; |
|
118 |
+ } |
|
119 |
+ else { |
|
120 |
+ flash(message => 'Success: a key is deleted'); |
|
121 |
+ $self->redirect_to('current'); |
|
122 |
+ } |
|
123 |
+ } |
|
105 | 124 |
} |
106 | 125 |
|
107 | 126 |
my $keys = app->dbi->model('ssh_public_key')->select(where => {user_id => $user})->all; |
... | ... |
@@ -144,22 +163,25 @@ |
144 | 163 |
This is a list of SSH keys associated with your account. Remove any keys that you do not recognize. |
145 | 164 |
</div> |
146 | 165 |
% for my $key (@$keys) { |
147 |
- <div class="border-gray" style="border-top:none;"> |
|
148 |
- <div class="row"> |
|
149 |
- <div class="span7" style="width:600px"> |
|
150 |
- <div style="font-size:15px;padding:10px"> |
|
151 |
- <div> |
|
152 |
- <b><%= $key->{title} %></b> |
|
166 |
+ <form action="<%= url_for->query(op => 'delete') %>" method="post" style="margin:0;padding:0"> |
|
167 |
+ <div class="border-gray" style="border-top:none;"> |
|
168 |
+ <div class="row"> |
|
169 |
+ <div class="span7" style="width:600px"> |
|
170 |
+ <div style="font-size:15px;padding:10px"> |
|
171 |
+ <div> |
|
172 |
+ <b><%= $key->{title} %></b> |
|
173 |
+ </div> |
|
153 | 174 |
</div> |
154 | 175 |
</div> |
155 |
- </div> |
|
156 |
- <div class="span2"> |
|
157 |
- <div style="padding:5px;text-align:right"> |
|
158 |
- <a class="btn btn-danger" href="javascript:void(0)">Delete</a> |
|
176 |
+ <div class="span2"> |
|
177 |
+ <div style="padding:5px;text-align:right"> |
|
178 |
+ <a class="btn btn-danger" href="javascript:void(0)" onclick="$(this).closest('form').submit()">Delete</a> |
|
179 |
+ <%= hidden_field 'row-id' => $key->{row_id} %> |
|
180 |
+ </div> |
|
159 | 181 |
</div> |
160 | 182 |
</div> |
161 | 183 |
</div> |
162 |
- </div> |
|
184 |
+ </form> |
|
163 | 185 |
% } |
164 | 186 |
% } else { |
165 | 187 |
<div class="border-gray" style="margin-bottom:30px;border-top:none;padding:10px"> |