瀏覽代碼

Added key modal to recipients page

Will Browning 6 年之前
父節點
當前提交
f590b2199b

+ 27 - 0
app/Http/Controllers/EncryptedRecipientController.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Resources\RecipientResource;
+use Illuminate\Http\Request;
+
+class EncryptedRecipientController extends Controller
+{
+    public function store(Request $request)
+    {
+        $recipient = user()->recipients()->findOrFail($request->id);
+
+        $recipient->update(['should_encrypt' => true]);
+
+        return new RecipientResource($recipient);
+    }
+
+    public function destroy($id)
+    {
+        $recipient = user()->recipients()->findOrFail($id);
+
+        $recipient->update(['should_encrypt' => false]);
+
+        return new RecipientResource($recipient);
+    }
+}

+ 50 - 0
app/Http/Controllers/RecipientKeyController.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\UpdateRecipientKeyRequest;
+use App\Http\Resources\RecipientResource;
+
+class RecipientKeyController extends Controller
+{
+    protected $gnupg;
+
+    public function __construct()
+    {
+        $this->gnupg = new \gnupg();
+    }
+
+    public function update(UpdateRecipientKeyRequest $request, $id)
+    {
+        $recipient = user()->recipients()->findOrFail($id);
+
+        $info = $this->gnupg->import($request->key_data);
+
+        if (!$info || !$info['imported']) {
+            return response('Key could not be imported', 404);
+        }
+
+        $recipient->update([
+            'should_encrypt' => true,
+            'fingerprint' => $info['fingerprint']
+        ]);
+
+        return new RecipientResource($recipient->fresh());
+    }
+
+    public function destroy($id)
+    {
+        $recipient = user()->recipients()->findOrFail($id);
+
+        if (!$this->gnupg->deletekey($recipient->fingerprint)) {
+            return response('Key could not be deleted', 404);
+        }
+
+        $recipient->update([
+            'should_encrypt' => false,
+            'fingerprint' => null
+        ]);
+
+        return response('', 204);
+    }
+}

+ 33 - 0
app/Http/Requests/UpdateRecipientKeyRequest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class UpdateRecipientKeyRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'key_data' => [
+                'string',
+                'regex:/-----BEGIN PGP PUBLIC KEY BLOCK-----([A-Za-z0-9+=\/\n]+)-----END PGP PUBLIC KEY BLOCK-----/i'
+            ]
+        ];
+    }
+}

+ 2 - 0
app/Http/Resources/AliasResource.php

@@ -11,11 +11,13 @@ class AliasResource extends JsonResource
         return [
             'id' => $this->id,
             'user_id' => $this->user_id,
+            'domain_id' => $this->domain_id,
             'email' => $this->email,
             'active' => $this->active,
             'description' => $this->description,
             'emails_forwarded' => $this->emails_forwarded,
             'emails_blocked' => $this->emails_blocked,
+            'emails_replied' => $this->emails_replied,
             'recipients' => $this->recipients,
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),

+ 3 - 1
app/Http/Resources/RecipientResource.php

@@ -11,8 +11,10 @@ class RecipientResource extends JsonResource
         return [
             'id' => $this->id,
             'user_id' => $this->user_id,
-            'alias_id' => $this->alias_id,
             'email' => $this->email,
+            'should_encrypt' => $this->should_encrypt,
+            'fingerprint' => $this->fingerprint,
+            'email_verified_at' => $this->email_verified_at ? $this->email_verified_at->toDateTimeString() : null,
             'aliases' => $this->aliases,
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),

+ 6 - 0
resources/js/components/Icon.vue

@@ -92,6 +92,12 @@
     ></path>
   </svg>
 
+  <svg v-else-if="name === 'fingerprint'" xmlns="http://www.w3.org/2000/svg" viewBox="-5 -2 24 24">
+    <path
+      d="M2 7a1 1 0 1 1-2 0 7 7 0 1 1 14 0v2a1 1 0 0 1-2 0V7A5 5 0 1 0 2 7zm3 3a1 1 0 0 1-2 0V7a4 4 0 1 1 8 0 1 1 0 0 1-2 0 2 2 0 1 0-4 0v3zm-2 3a1 1 0 0 1 2 0 2 2 0 1 0 4 0v-3a1 1 0 1 1 2 0v3a4 4 0 1 1-8 0zm3-5a1 1 0 1 1 2 0v4a1 1 0 0 1-2 0V8zm-6 3a1 1 0 0 1 2 0v2a5 5 0 0 0 10 0 1 1 0 0 1 2 0 7 7 0 0 1-14 0v-2z"
+    ></path>
+  </svg>
+
   <svg
     v-else-if="name === 'blocked'"
     xmlns="http://www.w3.org/2000/svg"

+ 170 - 1
resources/js/pages/Recipients.vue

@@ -97,6 +97,11 @@
               </div>
             </div>
           </th>
+          <th class="p-4">
+            <div class="flex items-center">
+              Encryption
+            </div>
+          </th>
           <th class="p-4" colspan="2">
             <div class="flex items-center">
               Verified
@@ -174,6 +179,28 @@
               }}</span>
             </div>
           </td>
+          <td class="border-grey-200 border-t">
+            <div class="p-4 flex items-center focus:text-indigo-500 text-sm">
+              <span v-if="recipient.fingerprint" class="flex">
+                <Toggle
+                  v-model="recipient.should_encrypt"
+                  @on="turnOnEncryption(recipient)"
+                  @off="turnOffEncryption(recipient)"
+                />
+                <icon
+                  name="fingerprint"
+                  class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-200 fill-current ml-2"
+                  :data-tippy-content="recipient.fingerprint"
+                  v-clipboard="() => recipient.fingerprint"
+                  v-clipboard:success="clipboardSuccess"
+                  v-clipboard:error="clipboardError"
+                />
+              </span>
+              <button v-else @click="openRecipientKeyModal(recipient)" class="focus:outline-none">
+                Add GPG key
+              </button>
+            </div>
+          </td>
           <td class="border-grey-200 border-t">
             <div class="p-4 flex items-center focus:text-indigo-500 text-sm">
               <span
@@ -208,7 +235,7 @@
         <tr v-if="queriedRecipients.length === 0">
           <td
             class="border-grey-200 border-t p-4 text-center h-24 text-lg text-grey-700"
-            colspan="4"
+            colspan="5"
           >
             No recipients found for that search!
           </td>
@@ -256,6 +283,47 @@
       </div>
     </Modal>
 
+    <Modal :open="addRecipientKeyModalOpen" @close="closeRecipientKeyModal">
+      <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
+        <h2
+          class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
+        >
+          Add Public GPG Key
+        </h2>
+        <p class="mt-4 text-grey-700">Enter your <b>PUBLIC</b> in the text area below.</p>
+        <div class="mt-6">
+          <p v-show="errors.recipientKey" class="mb-3 text-red-500">
+            {{ errors.recipientKey }}
+          </p>
+          <textarea
+            v-model="recipientKey"
+            type="email"
+            class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
+            :class="errors.recipientKey ? 'border-red-500' : ''"
+            placeholder="Begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'"
+            rows="15"
+            autofocus
+          >
+          </textarea>
+          <button
+            type="button"
+            @click="validateRecipientKey"
+            class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
+            :class="addRecipientKeyLoading ? 'cursor-not-allowed' : ''"
+            :disabled="addRecipientKeyLoading"
+          >
+            Add Key
+          </button>
+          <button
+            @click="closeRecipientKeyModal"
+            class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
+          >
+            Cancel
+          </button>
+        </div>
+      </div>
+    </Modal>
+
     <Modal :open="deleteRecipientModalOpen" @close="closeDeleteModal">
       <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
         <h2
@@ -288,6 +356,7 @@
 
 <script>
 import Modal from './../components/Modal.vue'
+import Toggle from './../components/Toggle.vue'
 import tippy from 'tippy.js'
 
 export default {
@@ -307,6 +376,7 @@ export default {
   },
   components: {
     Modal,
+    Toggle,
   },
   created() {
     this.defaultRecipient = _.find(this.recipients, ['id', this.user.default_recipient_id])
@@ -320,12 +390,16 @@ export default {
       recipients: this.initialRecipients,
       defaultRecipient: {},
       newRecipient: '',
+      recipientKey: '',
       search: '',
       addRecipientLoading: false,
       addRecipientModalOpen: false,
       recipientIdToDelete: null,
       deleteRecipientLoading: false,
       deleteRecipientModalOpen: false,
+      addRecipientKeyLoading: false,
+      addRecipientKeyModalOpen: false,
+      recipientToAddKey: {},
       resendVerificationLoading: false,
       currentSort: 'created_at',
       currentSortDir: 'desc',
@@ -336,6 +410,9 @@ export default {
     queriedRecipients: _.debounce(function() {
       this.addTooltips()
     }, 50),
+    addRecipientKeyModalOpen: _.debounce(function() {
+      this.addTooltips()
+    }, 50),
   },
   computed: {
     queriedRecipients() {
@@ -442,6 +519,86 @@ export default {
           this.deleteRecipientModalOpen = false
         })
     },
+    validateRecipientKey(e) {
+      this.errors = {}
+
+      if (!this.recipientKey) {
+        this.errors.recipientKey = 'Key required'
+      } else if (!this.validKey(this.recipientKey)) {
+        this.errors.recipientKey = 'Valid Key required'
+      }
+
+      if (!this.errors.recipientKey) {
+        this.addRecipientKey()
+      }
+
+      e.preventDefault()
+    },
+    addRecipientKey() {
+      this.addRecipientKeyLoading = true
+
+      axios
+        .patch(
+          `/recipient-keys/${this.recipientToAddKey.id}`,
+          JSON.stringify({
+            key_data: this.recipientKey,
+          }),
+          {
+            headers: { 'Content-Type': 'application/json' },
+          }
+        )
+        .then(({ data }) => {
+          this.addRecipientKeyLoading = false
+
+          let recipient = _.find(this.recipients, ['id', this.recipientToAddKey.id])
+          recipient.shoud_encrypt = data.data.should_encrypt
+          recipient.fingerprint = data.data.fingerprint
+
+          this.recipientKey = ''
+          this.addRecipientKeyModalOpen = false
+          this.success(
+            `Key Successfully Added for ${
+              this.recipientToAddKey.email
+            }. Make sure to check the fingerprint is correct!`
+          )
+        })
+        .catch(error => {
+          this.addRecipientKeyLoading = false
+          if (error.response !== undefined) {
+            this.error(error.response.data.message)
+          } else {
+            this.error()
+          }
+        })
+    },
+    turnOnEncryption(recipient) {
+      axios
+        .post(
+          `/encrypted-recipients`,
+          JSON.stringify({
+            id: recipient.id,
+          }),
+          {
+            headers: { 'Content-Type': 'application/json' },
+          }
+        )
+        .then(response => {
+          //
+        })
+        .catch(error => {
+          this.error()
+        })
+    },
+    turnOffEncryption(recipient) {
+      axios
+        .delete(`/encrypted-recipients/${recipient.id}`)
+        .then(response => {
+          //
+        })
+        .catch(error => {
+          this.error()
+        })
+    },
     sort(col, dir) {
       if (this.currentSort === col && this.currentSortDir === dir) {
         this.currentSort = 'created_at'
@@ -453,6 +610,14 @@ export default {
 
       this.reSort()
     },
+    openRecipientKeyModal(recipient) {
+      this.addRecipientKeyModalOpen = true
+      this.recipientToAddKey = recipient
+    },
+    closeRecipientKeyModal() {
+      this.addRecipientKeyModalOpen = false
+      this.recipientToAddKey = {}
+    },
     reSort() {
       this.recipients = _.orderBy(this.recipients, [this.currentSort], [this.currentSortDir])
     },
@@ -460,6 +625,10 @@ export default {
       let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
       return re.test(email)
     },
+    validKey(key) {
+      let re = /-----BEGIN PGP PUBLIC KEY BLOCK-----([A-Za-z0-9+=\/\n]+)-----END PGP PUBLIC KEY BLOCK-----/i
+      return re.test(key)
+    },
     clipboardSuccess() {
       this.success('Copied to clipboard')
     },

+ 6 - 0
routes/web.php

@@ -27,6 +27,12 @@ Route::middleware(['auth', 'verified', '2fa'])->group(function () {
     Route::post('/recipients', 'RecipientController@store')->name('recipients.store');
     Route::delete('/recipients/{id}', 'RecipientController@destroy')->name('recipients.destroy');
 
+    Route::patch('/recipient-keys/{id}', 'RecipientKeyController@update')->name('recipient_keys.update');
+    Route::delete('/recipient-keys/{id}', 'RecipientKeyController@destroy')->name('recipient_keys.destroy');
+
+    Route::post('/encrypted-recipients', 'EncryptedRecipientController@store')->name('encrypted_recipients.store');
+    Route::delete('/encrypted-recipients/{id}', 'EncryptedRecipientController@destroy')->name('encrypted_recipients.destroy');
+
     Route::post('/alias-recipients', 'AliasRecipientController@store')->name('alias_recipients.store');
 
     Route::get('/recipients/{id}/email/resend', 'RecipientVerificationController@resend')->name('recipient_verification.resend');

+ 100 - 0
tests/Feature/RecipientsTest.php

@@ -232,4 +232,104 @@ class RecipientsTest extends TestCase
 
         $response2->assertStatus(429);
     }
+
+    /** @test */
+    public function user_can_add_gpg_key_to_recipient()
+    {
+        $gnupg = new \gnupg();
+        $gnupg->deletekey('26A987650243B28802524E2F809FD0D502E2F695');
+
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $response = $this->json('PATCH', '/recipient-keys/'.$recipient->id, [
+            'key_data' => file_get_contents(base_path('tests/keys/AnonAddyPublicKey.asc'))
+        ]);
+
+        $response->assertStatus(200);
+        $this->assertTrue($response->getData()->data->should_encrypt);
+    }
+
+    /** @test */
+    public function gpg_key_must_be_correct_format()
+    {
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $response = $this->json('PATCH', '/recipient-keys/'.$recipient->id, [
+            'key_data' => 'Invalid Key Data'
+        ]);
+
+        $response
+            ->assertStatus(422)
+            ->assertJsonValidationErrors('key_data');
+    }
+
+    /** @test */
+    public function gpg_key_must_be_valid()
+    {
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id
+        ]);
+
+        $response = $this->json('PATCH', '/recipient-keys/'.$recipient->id, [
+            'key_data' => file_get_contents(base_path('tests/keys/InvalidAnonAddyPublicKey.asc'))
+        ]);
+
+        $response
+            ->assertStatus(404);
+    }
+
+    /** @test */
+    public function user_can_remove_gpg_key_from_recipient()
+    {
+        $gnupg = new \gnupg();
+        $gnupg->import(file_get_contents(base_path('tests/keys/AnonAddyPublicKey.asc')));
+
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id,
+            'should_encrypt' => true,
+            'fingerprint' => '26A987650243B28802524E2F809FD0D502E2F695'
+        ]);
+
+        $response = $this->json('DELETE', '/recipient-keys/'.$recipient->id);
+
+        $response->assertStatus(204);
+        $this->assertNull($this->user->recipients[0]->fingerprint);
+        $this->assertFalse($this->user->recipients[0]->should_encrypt);
+    }
+
+    /** @test */
+    public function user_can_turn_on_encryption_for_recipient()
+    {
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id,
+            'should_encrypt' => false,
+            'fingerprint' => '26A987650243B28802524E2F809FD0D502E2F695'
+        ]);
+
+        $response = $this->json('POST', '/encrypted-recipients/', [
+            'id' => $recipient->id
+        ]);
+
+        $response->assertStatus(200);
+        $this->assertEquals(true, $response->getData()->data->should_encrypt);
+    }
+
+    /** @test */
+    public function user_can_turn_off_encryption_for_recipient()
+    {
+        $recipient = factory(Recipient::class)->create([
+            'user_id' => $this->user->id,
+            'should_encrypt' => true,
+            'fingerprint' => '26A987650243B28802524E2F809FD0D502E2F695'
+        ]);
+
+        $response = $this->json('DELETE', '/encrypted-recipients/'.$recipient->id);
+
+        $response->assertStatus(200);
+        $this->assertEquals(false, $response->getData()->data->should_encrypt);
+    }
 }

+ 63 - 0
tests/keys/AnonAddyPublicKey.asc

@@ -0,0 +1,63 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=e75t
+-----END PGP PUBLIC KEY BLOCK-----

+ 63 - 0
tests/keys/InvalidAnonAddyPublicKey.asc

@@ -0,0 +1,63 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=e75t
+-----END PGP PUBLIC KEY BLOCK-----