Add the possibility to save mtn public keys per user
* src/IDF/Key.php: new column "type" which is either "ssh" or "mtn"; utility functions to query the mtn key name and id as well as all available key types for the current IDF installation * src/IDF/Migrations/16KeyType.php: needed migration script * src/IDF/Plugin/SyncGit/Cron.php: ensure only SSH keys are handled * adapt forms and templates accordingly
This commit is contained in:
		| @@ -82,16 +82,22 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form | ||||
|                                                                     ), | ||||
|                                             )); | ||||
|  | ||||
|         $this->fields['ssh_key'] = new Pluf_Form_Field_Varchar( | ||||
|         $this->fields['public_key'] = new Pluf_Form_Field_Varchar( | ||||
|                                       array('required' => false, | ||||
|                                             'label' => __('Add a public SSH key'), | ||||
|                                             'label' => __('Add a public key'), | ||||
|                                             'initial' => '', | ||||
|                                             'widget_attrs' => array('rows' => 3, | ||||
|                                                                     'cols' => 40), | ||||
|                                             'widget' => 'Pluf_Form_Widget_TextareaInput', | ||||
|                                             'help_text' => __('Be careful to provide the public key and not the private key!') | ||||
|                                             )); | ||||
|          | ||||
|         $this->fields['public_key_type'] = new Pluf_Form_Field_Varchar( | ||||
|                                    array('required' => true, | ||||
|                                          'label' => __('Key type'), | ||||
|                                          'initial' => 'ssh', | ||||
|                                          'widget_attrs' => array('choices' => IDF_Key::getAvailableKeyTypes()), | ||||
|                                          'widget' => 'Pluf_Form_Widget_SelectInput', | ||||
|                                          )); | ||||
|  | ||||
|     } | ||||
|  | ||||
| @@ -138,10 +144,11 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form | ||||
|         Pluf_Signal::send('Pluf_User::passwordUpdated', | ||||
|                           'IDF_Form_Admin_UserCreate', $params); | ||||
|         // Create the ssh key as needed | ||||
|         if ('' !== $this->cleaned_data['ssh_key']) { | ||||
|         if ('' !== $this->cleaned_data['public_key']) { | ||||
|             $key = new IDF_Key(); | ||||
|             $key->user = $user; | ||||
|             $key->content = $this->cleaned_data['ssh_key']; | ||||
|             $key->content = $this->cleaned_data['public_key']; | ||||
|             $key->type = $this->cleaned_data['public_key_type']; | ||||
|             $key->create(); | ||||
|         } | ||||
|         // Send an email to the user with the password | ||||
| @@ -162,11 +169,6 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form | ||||
|         return $user; | ||||
|     } | ||||
|  | ||||
|     function clean_ssh_key() | ||||
|     { | ||||
|         return IDF_Form_UserAccount::checkSshKey($this->cleaned_data['ssh_key']); | ||||
|     } | ||||
|  | ||||
|     function clean_last_name() | ||||
|     { | ||||
|         $last_name = trim($this->cleaned_data['last_name']); | ||||
| @@ -211,4 +213,16 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form | ||||
|         } | ||||
|         return $this->cleaned_data['login']; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks whether any given public key is valid | ||||
|      */ | ||||
|     public function clean() | ||||
|     { | ||||
|         $this->cleaned_data['public_key'] = | ||||
|             IDF_Form_UserAccount::checkPublicKey($this->cleaned_data['public_key'], | ||||
|                                                  $this->cleaned_data['public_key_type']); | ||||
|  | ||||
|         return $this->cleaned_data; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -92,16 +92,22 @@ class IDF_Form_UserAccount  extends Pluf_Form | ||||
|                                                                     ), | ||||
|                                             )); | ||||
|  | ||||
|         $this->fields['ssh_key'] = new Pluf_Form_Field_Varchar( | ||||
|         $this->fields['public_key'] = new Pluf_Form_Field_Varchar( | ||||
|                                       array('required' => false, | ||||
|                                             'label' => __('Add a public SSH key'), | ||||
|                                             'label' => __('Add a public key'), | ||||
|                                             'initial' => '', | ||||
|                                             'widget_attrs' => array('rows' => 3, | ||||
|                                                                     'cols' => 40), | ||||
|                                             'widget' => 'Pluf_Form_Widget_TextareaInput', | ||||
|                                             'help_text' => __('Be careful to provide your public key and not your private key!') | ||||
|                                             )); | ||||
|          | ||||
|         $this->fields['public_key_type'] = new Pluf_Form_Field_Varchar( | ||||
|                                            array('required' => true, | ||||
|                                                  'label' => __('Key type'), | ||||
|                                                  'initial' => 'ssh', | ||||
|                                                  'widget_attrs' => array('choices' => IDF_Key::getAvailableKeyTypes()), | ||||
|                                                  'widget' => 'Pluf_Form_Widget_SelectInput', | ||||
|                                                  )); | ||||
|  | ||||
|     } | ||||
|  | ||||
| @@ -151,10 +157,11 @@ class IDF_Form_UserAccount  extends Pluf_Form | ||||
|         } | ||||
|         $this->user->setFromFormData($this->cleaned_data); | ||||
|         // Add key as needed. | ||||
|         if ('' !== $this->cleaned_data['ssh_key']) { | ||||
|         if ('' !== $this->cleaned_data['public_key']) { | ||||
|             $key = new IDF_Key(); | ||||
|             $key->user = $this->user; | ||||
|             $key->content = $this->cleaned_data['ssh_key']; | ||||
|             $key->content = $this->cleaned_data['public_key']; | ||||
|             $key->type = $this->cleaned_data['public_key_type']; | ||||
|             if ($commit) { | ||||
|                 $key->create(); | ||||
|             } | ||||
| @@ -190,56 +197,106 @@ class IDF_Form_UserAccount  extends Pluf_Form | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check an ssh key. | ||||
|      * Check arbitrary public keys. | ||||
|      * | ||||
|      * It will throw a Pluf_Form_Invalid exception if it cannot | ||||
|      * validate the key. | ||||
|      * | ||||
|      * @param $key string The key | ||||
|      * @param $key string The type ('ssh' or 'mtn') | ||||
|      * @param $user int The user id of the user of the key (0) | ||||
|      * @return string The clean key | ||||
|      */ | ||||
|     public static function checkSshKey($key, $user=0) | ||||
|     public static function checkPublicKey($key, $type, $user=0) | ||||
|     { | ||||
|         $key = trim($key); | ||||
|         if (strlen($key) == 0) { | ||||
|         if (strlen($key) == 0) | ||||
|         { | ||||
|             return ''; | ||||
|         } | ||||
|  | ||||
|         if ($type == 'ssh') | ||||
|         { | ||||
|             $key = str_replace(array("\n", "\r"), '', $key); | ||||
|         if (!preg_match('#^ssh\-[a-z]{3}\s(\S+)\s\S+$#', $key, $matches)) { | ||||
|             throw new Pluf_Form_Invalid(__('The format of the key is not valid. It must start with ssh-dss or ssh-rsa, a long string on a single line and at the end a comment.')); | ||||
|             if (!preg_match('#^ssh\-[a-z]{3}\s\S+\s\S+$#', $key)) | ||||
|             { | ||||
|                 throw new Pluf_Form_Invalid( | ||||
|                     __('The format of the key is not valid. It must start '. | ||||
|                        'with ssh-dss or ssh-rsa, a long string on a single '. | ||||
|                        'line and at the end a comment.') | ||||
|                 ); | ||||
|             } | ||||
|         if (Pluf::f('idf_strong_key_check', false)) { | ||||
|             if (Pluf::f('idf_strong_key_check', false)) | ||||
|             { | ||||
|                 $tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key'; | ||||
|                 file_put_contents($tmpfile, $key, LOCK_EX); | ||||
|                 $cmd = Pluf::f('idf_exec_cmd_prefix', ''). | ||||
|                     'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1'; | ||||
|                 exec($cmd, $out, $return); | ||||
|                 unlink($tmpfile); | ||||
|             if ($return != 0) { | ||||
|                 throw new Pluf_Form_Invalid(__('Please check the key as it does not appears to be a valid key.')); | ||||
|                 if ($return != 0) | ||||
|                 { | ||||
|                     throw new Pluf_Form_Invalid( | ||||
|                         __('Please check the key as it does not appears '. | ||||
|                            'to be a valid key.') | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else if ($type == 'mtn') | ||||
|         { | ||||
|             if (!preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key)) | ||||
|             { | ||||
|                 throw new Pluf_Form_Invalid( | ||||
|                     __('The format of the key is not valid. It must start '. | ||||
|                        'with [pubkey KEYNAME], contain a long string on a single '. | ||||
|                        'line and end with [end] in the final third line.') | ||||
|                 ); | ||||
|             } | ||||
|             if (Pluf::f('idf_strong_key_check', false)) | ||||
|             { | ||||
|                 // if monotone can read it, it should be valid | ||||
|                 $mtn_opts = implode(' ', Pluf::f('mtn_opts', array())); | ||||
|                 $cmd = Pluf::f('idf_exec_cmd_prefix', ''). | ||||
|                     sprintf('%s %s -d :memory: read >/tmp/php-out 2>&1', | ||||
|                             Pluf::f('mtn_path', 'mtn'), $mtn_opts); | ||||
|                 $fp = popen($cmd, 'w'); | ||||
|                 fwrite($fp, $key); | ||||
|                 $return = pclose($fp); | ||||
|  | ||||
|                 if ($return != 0) | ||||
|                 { | ||||
|                        throw new Pluf_Form_Invalid( | ||||
|                         __('Please check the key as it does not appears '. | ||||
|                            'to be a valid key.') | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new Pluf_Form_Invalid(__('Unknown key type')); | ||||
|         } | ||||
|  | ||||
|         // If $user, then check if not the same key stored | ||||
|         if ($user) { | ||||
|         if ($user) | ||||
|         { | ||||
|             $ruser = Pluf::factory('Pluf_User', $user); | ||||
|             if ($ruser->id > 0) { | ||||
|                 $sql = new Pluf_SQL('content=%s', array($key)); | ||||
|             if ($ruser->id > 0) | ||||
|             { | ||||
|                 $sql = new Pluf_SQL('content=%s AND type=%s', array($key, $type)); | ||||
|                 $keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen())); | ||||
|                 if (count($keys) > 0) { | ||||
|                     throw new Pluf_Form_Invalid(__('You already have uploaded this SSH key.')); | ||||
|                 if (count($keys) > 0) | ||||
|                 { | ||||
|                     throw new Pluf_Form_Invalid( | ||||
|                         __('You already have uploaded this key.') | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return $key; | ||||
|     } | ||||
|  | ||||
|     function clean_ssh_key() | ||||
|     { | ||||
|         return self::checkSshKey($this->cleaned_data['ssh_key'],  | ||||
|                                  $this->user->id); | ||||
|     } | ||||
|  | ||||
|     function clean_last_name() | ||||
|     { | ||||
|         $last_name = trim($this->cleaned_data['last_name']); | ||||
| @@ -273,7 +330,8 @@ class IDF_Form_UserAccount  extends Pluf_Form | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check to see if the 2 passwords are the same. | ||||
|      * Check to see if the 2 passwords are the same and if any | ||||
|      * given public key is valid | ||||
|      */ | ||||
|     public function clean() | ||||
|     { | ||||
| @@ -285,6 +343,12 @@ class IDF_Form_UserAccount  extends Pluf_Form | ||||
|                 throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.')); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->cleaned_data['public_key'] = | ||||
|             self::checkPublicKey($this->cleaned_data['public_key'], | ||||
|                                  $this->cleaned_data['public_key_type'], | ||||
|                                  $this->user->id); | ||||
|  | ||||
|         return $this->cleaned_data; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
| # ***** END LICENSE BLOCK ***** */ | ||||
|  | ||||
| /** | ||||
|  * Storage of the SSH keys. | ||||
|  * Storage of the public keys (ssh or monotone). | ||||
|  * | ||||
|  */ | ||||
| class IDF_Key extends Pluf_Model | ||||
| @@ -52,7 +52,14 @@ class IDF_Key extends Pluf_Model | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Text', | ||||
|                                   'blank' => false, | ||||
|                                   'verbose' => __('ssh key'), | ||||
|                                   'verbose' => __('public key'), | ||||
|                                   ), | ||||
|                             'type' => | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Varchar', | ||||
|                                   'size' => 3, | ||||
|                                   'blank' => false, | ||||
|                                   'verbose' => __('key type'), | ||||
|                                   ), | ||||
|                             ); | ||||
|         // WARNING: Not using getSqlTable on the Pluf_User object to | ||||
| @@ -75,6 +82,46 @@ class IDF_Key extends Pluf_Model | ||||
|         return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55))); | ||||
|     } | ||||
|  | ||||
|     private function parseMonotoneKeyData() | ||||
|     { | ||||
|         if ($this->type != "mtn") | ||||
|             throw new IDF_Exception("key is not a monotone key type"); | ||||
|  | ||||
|         preg_match("#^\[pubkey ([^\]]+)\]\s*(\S+)\s*\[end\]$#", $this->content, $m); | ||||
|         if (count($m) != 3) | ||||
|             throw new IDF_Exception("invalid key data detected"); | ||||
|  | ||||
|         return array($m[1], $m[2]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the key name of the key, i.e. most of the time the email | ||||
|      * address, which not neccessarily has to be unique across a project. | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     function getMonotoneKeyName() | ||||
|     { | ||||
|         list($keyName, ) = $this->parseMonotoneKeyData(); | ||||
|         return $keyName; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This function should be used to calculate the key id from the | ||||
|      * public key hash for authentication purposes. This avoids clashes | ||||
|      * in case the key name is not unique across the project | ||||
|      * | ||||
|      * And yes, this is actually how monotone itself calculates the key | ||||
|      * id... | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     function getMonotoneKeyId() | ||||
|     { | ||||
|         list($keyName, $keyData) = $this->parseMonotoneKeyData(); | ||||
|         return sha1($keyName.":".$keyData); | ||||
|     } | ||||
|  | ||||
|     function postSave($create=false) | ||||
|     { | ||||
|         /** | ||||
| @@ -89,7 +136,7 @@ class IDF_Key extends Pluf_Model | ||||
|          * [description] | ||||
|          * | ||||
|          * This signal allows an application to perform special | ||||
|          * operations after the saving of a SSH Key. | ||||
|          * operations after the saving of a public Key. | ||||
|          * | ||||
|          * [parameters] | ||||
|          * | ||||
| @@ -128,4 +175,19 @@ class IDF_Key extends Pluf_Model | ||||
|                           'IDF_Key', $params); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an associative array with available key types for this | ||||
|      * idf installation, ready for consumption for a <select> widget | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public static function getAvailableKeyTypes() | ||||
|     { | ||||
|         $key_types = array(__("SSH") => 'ssh'); | ||||
|         if (array_key_exists('mtn', Pluf::f('allowed_scm', array()))) | ||||
|         { | ||||
|             $key_types[__("monotone")] = 'mtn'; | ||||
|         } | ||||
|         return $key_types; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										55
									
								
								src/IDF/Migrations/16KeyType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/IDF/Migrations/16KeyType.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| <?php | ||||
| /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ | ||||
| /* | ||||
| # ***** BEGIN LICENSE BLOCK ***** | ||||
| # This file is part of InDefero, an open source project management application. | ||||
| # Copyright (C) 2008 Céondo Ltd and contributors. | ||||
| # | ||||
| # InDefero is free software; you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation; either version 2 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # InDefero is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program; if not, write to the Free Software | ||||
| # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| # | ||||
| # ***** END LICENSE BLOCK ***** */ | ||||
|  | ||||
| /** | ||||
|  * Add the type column in the keys table. | ||||
|  */ | ||||
|  | ||||
| function IDF_Migrations_16KeyType_up($params=null) | ||||
| { | ||||
|     $table = Pluf::factory('IDF_Keys')->getSqlTable(); | ||||
|     $sql = array(); | ||||
|     $sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "type" VARCHAR(3) DEFAULT \'ssh\''; | ||||
|     $sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `type` VARCHAR(3) DEFAULT \'ssh\''; | ||||
|     $db = Pluf::db(); | ||||
|     $engine = Pluf::f('db_engine'); | ||||
|     if (!isset($sql[$engine])) { | ||||
|         throw new Exception('SQLite complex migration not supported.'); | ||||
|     } | ||||
|     $db->execute($sql[$engine]); | ||||
| } | ||||
|  | ||||
| function IDF_Migrations_16KeyType_down($params=null) | ||||
| { | ||||
|     $table = Pluf::factory('IDF_Keys')->getSqlTable(); | ||||
|     $sql = array(); | ||||
|     $sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "type"'; | ||||
|     $sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `type`'; | ||||
|     $db = Pluf::db(); | ||||
|     $engine = Pluf::f('db_engine'); | ||||
|     if (!isset($sql[$engine])) { | ||||
|         throw new Exception('SQLite complex migration not supported.'); | ||||
|     } | ||||
|     $db->execute($sql[$engine]); | ||||
|  | ||||
| } | ||||
| @@ -48,7 +48,7 @@ class IDF_Plugin_SyncGit_Cron | ||||
|         $out = ''; | ||||
|         $keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user')); | ||||
|         foreach ($keys as $key) { | ||||
|             if (strlen($key->content) > 40 // minimal check | ||||
|             if ($key->type == 'ssh' && strlen($key->content) > 40 // minimal check | ||||
|                 and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) { | ||||
|                 $content = trim(str_replace(array("\n", "\r"), '', $key->content)); | ||||
|                 $out .= sprintf($template, $cmd, $key->login, $content)."\n"; | ||||
|   | ||||
| @@ -134,7 +134,7 @@ class IDF_Views_User | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Delete a SSH key. | ||||
|      * Delete a public key. | ||||
|      * | ||||
|      * This is redirecting to the preferences | ||||
|      */ | ||||
| @@ -148,7 +148,7 @@ class IDF_Views_User | ||||
|                 return new Pluf_HTTP_Response_Forbidden($request); | ||||
|             } | ||||
|             $key->delete(); | ||||
|             $request->user->setMessage(__('The SSH key has been deleted.')); | ||||
|             $request->user->setMessage(__('The public key has been deleted.')); | ||||
|         } | ||||
|         return new Pluf_HTTP_Response_Redirect($url); | ||||
|     } | ||||
|   | ||||
| @@ -43,10 +43,14 @@ | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
| <th>{$form.f.ssh_key.labelTag}:</th> | ||||
| <td>{if $form.f.ssh_key.errors}{$form.f.ssh_key.fieldErrors}{/if} | ||||
| {$form.f.ssh_key|unsafe}<br /> | ||||
| <span class="helptext">{$form.f.ssh_key.help_text}</span> | ||||
| <th>{$form.f.public_key.labelTag}:</th> | ||||
| <td>{if $form.f.public_key.errors}{$form.f.public_key.fieldErrors}{/if} | ||||
| {$form.f.public_key|unsafe}<br /> | ||||
| {$form.f.public_key_type.labelTag} {$form.f.public_key_type|unsafe}<br /> | ||||
| <span class="helptext"> | ||||
|     {$form.f.public_key.help_text} | ||||
|     {$form.f.public_key_type.help_text} | ||||
| </span> | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
|   | ||||
| @@ -54,10 +54,14 @@ | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
| <th>{$form.f.ssh_key.labelTag}:</th> | ||||
| <td>{if $form.f.ssh_key.errors}{$form.f.ssh_key.fieldErrors}{/if} | ||||
| {$form.f.ssh_key|unsafe}<br /> | ||||
| <span class="helptext">{$form.f.ssh_key.help_text}</span> | ||||
| <th>{$form.f.public_key.labelTag}:</th> | ||||
| <td>{if $form.f.public_key.errors}{$form.f.public_key.fieldErrors}{/if} | ||||
| {$form.f.public_key|unsafe}<br /> | ||||
| {$form.f.public_key_type.labelTag} {$form.f.public_key_type|unsafe}<br /> | ||||
| <span class="helptext"> | ||||
|     {$form.f.public_key.help_text} | ||||
|     {$form.f.public_key_type.help_text} | ||||
| </span> | ||||
| </td> | ||||
| </tr> | ||||
| <tr class="pass-info" id="extra-password"> | ||||
| @@ -82,7 +86,7 @@ | ||||
|  | ||||
| {if count($keys)} | ||||
| <table summary=" " class="recent-issues"> | ||||
| <tr><th colspan="2">{trans 'Your Current SSH Keys'}</th></tr> | ||||
| <tr><th colspan="2">{trans 'Your Current Public Keys'}</th></tr> | ||||
| {foreach $keys as $key}<tr><td> | ||||
| <span class="mono">{$key.showCompact()}</span></td><td> <form class="star" method="post" action="{url 'IDF_Views_User::deleteKey', array($key.id)}"><input type="image" src="{media '/idf/img/trash.png'}" name="submit" value="{trans 'Delete this key'}" /></form> | ||||
| </td> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user