Showing 5 changed files with 171 additions and 12 deletions
+4
gitprep.conf
... ...
@@ -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.
+14
lib/Gitprep.pm
... ...
@@ -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);
+119
lib/Gitprep/Manager.pm
... ...
@@ -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 ||= {};
lock/authorized_keys
No changes.
+34 -12
templates/user-settings/ssh.html.ep
... ...
@@ -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">