mirror of https://github.com/docusealco/docuseal
				
				
				
			
							parent
							
								
									c9f39e7b1a
								
							
						
					
					
						commit
						d3a5f36555
					
				| @ -1,19 +1,109 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class EsignSettingsController < ApplicationController | class EsignSettingsController < ApplicationController | ||||||
|  |   DEFAULT_CERT_NAME = 'DocuSeal Self-Host Autogenerated' | ||||||
|  | 
 | ||||||
|  |   CertFormRecord = Struct.new(:name, :file, :password, keyword_init: true) do | ||||||
|  |     include ActiveModel::Validations | ||||||
|  | 
 | ||||||
|  |     def to_key | ||||||
|  |       [] | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def show | ||||||
|  |     cert_data = EncryptedConfig.find_by(account: current_account, | ||||||
|  |                                         key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {} | ||||||
|  | 
 | ||||||
|  |     default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present? | ||||||
|  | 
 | ||||||
|  |     custom_pkcs_list = (cert_data['custom'] || []).map do |e| | ||||||
|  |       { 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password']), | ||||||
|  |         'name' => e['name'], | ||||||
|  |         'status' => e['status'] } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @pkcs_list = [ | ||||||
|  |       if default_pkcs | ||||||
|  |         { | ||||||
|  |           'pkcs' => default_pkcs, | ||||||
|  |           'name' => DEFAULT_CERT_NAME, | ||||||
|  |           'status' => custom_pkcs_list.any? { |e| e['status'] == 'default' } ? 'validate' : 'default' | ||||||
|  |         } | ||||||
|  |       end, | ||||||
|  |       *custom_pkcs_list | ||||||
|  |     ].compact.reverse | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def new | ||||||
|  |     @cert_record = CertFormRecord.new | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def create |   def create | ||||||
|     pdfs = |     @cert_record = CertFormRecord.new(**cert_params) | ||||||
|       params[:files].map do |file| | 
 | ||||||
|         HexaPDF::Document.new(io: file.open) |     cert_configs = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY) | ||||||
|       end | 
 | ||||||
|  |     if cert_configs.value['custom']&.any? { |e| e['name'] == @cert_record.name } || | ||||||
|  |        @cert_record.name == DEFAULT_CERT_NAME | ||||||
|  | 
 | ||||||
|  |       @cert_record.errors.add(:name, 'already exists') | ||||||
|  | 
 | ||||||
|  |       return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), | ||||||
|  |                     status: :unprocessable_entity | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     save_new_cert!(cert_configs, @cert_record) | ||||||
|  | 
 | ||||||
|  |     redirect_to settings_esign_path, notice: 'Certificate has been successfully added!' | ||||||
|  |   rescue OpenSSL::PKCS12::PKCS12Error | ||||||
|  |     @cert_record.errors.add(:password, "is invalid. Make sure you're uploading a valid .p12 file") | ||||||
| 
 | 
 | ||||||
|     certs = Accounts.load_signing_certs(current_account) |     render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_entity | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def update | ||||||
|  |     cert_configs = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY) | ||||||
|  | 
 | ||||||
|  |     cert_configs.value['custom'].each { |e| e['status'] = 'validate' } | ||||||
|  |     custom_cert_data = cert_configs.value['custom'].find { |e| e['name'] == params[:name] } | ||||||
|  |     custom_cert_data['status'] = 'default' if custom_cert_data | ||||||
|  | 
 | ||||||
|  |     cert_configs.save! | ||||||
|  | 
 | ||||||
|  |     redirect_to settings_esign_path, notice: 'Default certificate has been selected' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def destroy | ||||||
|  |     cert_configs = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY) | ||||||
|  | 
 | ||||||
|  |     cert_configs.value['custom'].reject! { |e| e['name'] == params[:name] } | ||||||
|  | 
 | ||||||
|  |     cert_configs.save! | ||||||
|  | 
 | ||||||
|  |     redirect_to settings_esign_path, notice: 'Certificate has been removed' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   private | ||||||
|  | 
 | ||||||
|  |   def save_new_cert!(cert_configs, cert_record) | ||||||
|  |     pkcs = OpenSSL::PKCS12.new(cert_record.file.read, cert_record.password) | ||||||
|  | 
 | ||||||
|  |     cert_configs.value['custom'] ||= [] | ||||||
|  |     cert_configs.value['custom'].each { |e| e['status'] = 'validate' } | ||||||
|  |     cert_configs.value['custom'] << { | ||||||
|  |       data: Base64.urlsafe_encode64(pkcs.to_der), | ||||||
|  |       password: cert_record.password, | ||||||
|  |       name: cert_record.name, | ||||||
|  |       status: 'default' | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     cert_configs.save! | ||||||
|  |   end | ||||||
| 
 | 
 | ||||||
|     trusted_certs = [certs[:cert], certs[:sub_ca], certs[:root_ca]] |   def cert_params | ||||||
|  |     return {} if params[:esign_settings_controller_cert_form_record].blank? | ||||||
| 
 | 
 | ||||||
|     render turbo_stream: turbo_stream.replace('result', partial: 'result', |     params.require(:esign_settings_controller_cert_form_record).permit(:name, :file, :password) | ||||||
|                                                         locals: { pdfs:, files: params[:files], trusted_certs: }) |  | ||||||
|   rescue HexaPDF::MalformedPDFError |  | ||||||
|     render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result')) |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -0,0 +1,29 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class VerifyPdfSignatureController < ApplicationController | ||||||
|  |   def create | ||||||
|  |     pdfs = | ||||||
|  |       params[:files].map do |file| | ||||||
|  |         HexaPDF::Document.new(io: file.open) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |     cert_data = EncryptedConfig.find_by(account: current_account, | ||||||
|  |                                         key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {} | ||||||
|  | 
 | ||||||
|  |     default_pkcs = GenerateCertificate.load_pkcs(cert_data) | ||||||
|  | 
 | ||||||
|  |     custom_certs = (cert_data['custom'] || []).map do |e| | ||||||
|  |       OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password']) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     trusted_certs = [default_pkcs.certificate, | ||||||
|  |                      *default_pkcs.ca_certs, | ||||||
|  |                      *custom_certs.map(&:certificate), | ||||||
|  |                      *custom_certs.flat_map(&:ca_certs).compact] | ||||||
|  | 
 | ||||||
|  |     render turbo_stream: turbo_stream.replace('result', partial: 'result', | ||||||
|  |                                                         locals: { pdfs:, files: params[:files], trusted_certs: }) | ||||||
|  |   rescue HexaPDF::MalformedPDFError | ||||||
|  |     render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result')) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -1,41 +0,0 @@ | |||||||
| <div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0"> |  | ||||||
|   <%= render 'shared/settings_nav' %> |  | ||||||
|   <div class="flex-grow max-w-xl mx-auto"> |  | ||||||
|     <h1 class="text-4xl font-bold mb-4">PDF Signature</h1> |  | ||||||
|     <div id="result"> |  | ||||||
|       <p class="mb-2"> |  | ||||||
|         Upload signed PDF file to validate its signature: |  | ||||||
|       </p> |  | ||||||
|     </div> |  | ||||||
|     <%= form_for '', url: settings_esign_index_path, method: :post, html: { enctype: 'multipart/form-data' } do |f| %> |  | ||||||
|       <%= f.button type: 'submit', class: 'flex' do %> |  | ||||||
|         <div class="disabled mb-3"> |  | ||||||
|           <%= svg_icon('loader', class: 'w-5 h-5 animate-spin inline') %> |  | ||||||
|           Analyzing... |  | ||||||
|         </div> |  | ||||||
|       <% end %> |  | ||||||
|       <file-dropzone data-is-direct-upload="false" data-name="verify_attachments" data-submit-on-upload="true" class="w-full"> |  | ||||||
|         <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> |  | ||||||
|           <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> |  | ||||||
|             <div class="flex flex-col items-center"> |  | ||||||
|               <span data-target="file-dropzone.icon"> |  | ||||||
|                 <%= svg_icon('cloud_upload', class: 'w-10 h-10') %> |  | ||||||
|               </span> |  | ||||||
|               <span data-target="file-dropzone.loading" class="hidden"> |  | ||||||
|                 <%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %> |  | ||||||
|               </span> |  | ||||||
|               <div class="font-medium mb-1"> |  | ||||||
|                 Verify Signed PDF |  | ||||||
|               </div> |  | ||||||
|               <div class="text-xs"> |  | ||||||
|                 <span class="font-medium">Click to upload</span> or drag and drop files |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|             <input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple> |  | ||||||
|           </div> |  | ||||||
|         </label> |  | ||||||
|       </file-dropzone> |  | ||||||
|     <% end %> |  | ||||||
|   </div> |  | ||||||
|   <div class="w-0 md:w-52"></div> |  | ||||||
| </div> |  | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | <%= render 'shared/turbo_modal', title: 'Upload Certificate' do %> | ||||||
|  |   <%= form_for @cert_record, url: settings_esign_path, html: { class: 'space-y-4', enctype: 'multipart/form-data' }, data: { turbo_frame: :_top } do |f| %> | ||||||
|  |     <div class="space-y-2"> | ||||||
|  |       <div class="form-control"> | ||||||
|  |         <%= f.label :name, class: 'label' %> | ||||||
|  |         <%= f.text_field :name, required: true, class: 'base-input' %> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-control"> | ||||||
|  |         <%= f.label :file, class: 'label' %> | ||||||
|  |         <%= f.file_field :file, required: true %> | ||||||
|  |         <label class="label"> | ||||||
|  |           <span class="label-text-alt">Use a valid .der, .p12 or .pfx file.</span> | ||||||
|  |         </label> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-control"> | ||||||
|  |         <%= f.label :password, 'Password (optional)', class: 'label' %> | ||||||
|  |         <%= f.text_field :password, class: 'base-input' %> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-control pt-2"> | ||||||
|  |       <%= f.button button_title, class: 'base-button' %> | ||||||
|  |     </div> | ||||||
|  |   <% end %> | ||||||
|  | <% end %> | ||||||
| @ -0,0 +1,99 @@ | |||||||
|  | <div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0 md:space-x-10"> | ||||||
|  |   <%= render 'shared/settings_nav' %> | ||||||
|  |   <div class="flex-grow"> | ||||||
|  |     <div class="max-w-xl"> | ||||||
|  |       <h1 class="text-4xl font-bold mb-4">PDF Signature</h1> | ||||||
|  |       <div id="result"> | ||||||
|  |         <p class="mb-2"> | ||||||
|  |           Upload signed PDF file to validate its signature: | ||||||
|  |         </p> | ||||||
|  |       </div> | ||||||
|  |       <%= form_for '', url: verify_pdf_signature_index_path, method: :post, html: { enctype: 'multipart/form-data' } do |f| %> | ||||||
|  |         <%= f.button type: 'submit', class: 'flex' do %> | ||||||
|  |           <div class="disabled mb-3"> | ||||||
|  |             <%= svg_icon('loader', class: 'w-5 h-5 animate-spin inline') %> | ||||||
|  |             Analyzing... | ||||||
|  |           </div> | ||||||
|  |         <% end %> | ||||||
|  |         <file-dropzone data-is-direct-upload="false" data-name="verify_attachments" data-submit-on-upload="true" class="w-full"> | ||||||
|  |           <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed"> | ||||||
|  |             <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> | ||||||
|  |               <div class="flex flex-col items-center"> | ||||||
|  |                 <span data-target="file-dropzone.icon"> | ||||||
|  |                   <%= svg_icon('cloud_upload', class: 'w-10 h-10') %> | ||||||
|  |                 </span> | ||||||
|  |                 <span data-target="file-dropzone.loading" class="hidden"> | ||||||
|  |                   <%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %> | ||||||
|  |                 </span> | ||||||
|  |                 <div class="font-medium mb-1"> | ||||||
|  |                   Verify Signed PDF | ||||||
|  |                 </div> | ||||||
|  |                 <div class="text-xs"> | ||||||
|  |                   <span class="font-medium">Click to upload</span> or drag and drop files | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |               <input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple> | ||||||
|  |             </div> | ||||||
|  |           </label> | ||||||
|  |         </file-dropzone> | ||||||
|  |       <% end %> | ||||||
|  |     </div> | ||||||
|  |     <div class="flex justify-between items-end mb-4 mt-8"> | ||||||
|  |       <h2 class="text-3xl font-bold">Signing Certificates</h2> | ||||||
|  |       <%= link_to new_settings_esign_path, class: 'btn btn-primary btn-md', data: { turbo_frame: 'modal' } do %> | ||||||
|  |         <%= svg_icon('plus', class: 'w-6 h-6') %> | ||||||
|  |         <span>Upload Cert</span> | ||||||
|  |       <% end %> | ||||||
|  |     </div> | ||||||
|  |     <%= render 'alert' %> | ||||||
|  |     <div class="overflow-x-auto"> | ||||||
|  |       <table class="table w-full table-lg rounded-b-none overflow-hidden"> | ||||||
|  |         <thead class="bg-base-200"> | ||||||
|  |           <tr class="text-neutral uppercase"> | ||||||
|  |             <th> | ||||||
|  |               Name | ||||||
|  |             </th> | ||||||
|  |             <th> | ||||||
|  |               Valid To | ||||||
|  |             </th> | ||||||
|  |             <th> | ||||||
|  |               Status | ||||||
|  |             </th> | ||||||
|  |             <th class="text-right" width="1px"> | ||||||
|  |             </th> | ||||||
|  |           </tr> | ||||||
|  |         </thead> | ||||||
|  |         <tbody> | ||||||
|  |           <% @pkcs_list.each do |item| %> | ||||||
|  |             <tr scope="row" class="group"> | ||||||
|  |               <td> | ||||||
|  |                 <%= item['name'] %> | ||||||
|  |               </td> | ||||||
|  |               <td> | ||||||
|  |                 <%= l(item['pkcs'].certificate.not_after.to_date, format: :long, locale: current_account.locale) %> | ||||||
|  |               </td> | ||||||
|  |               <td> | ||||||
|  |                 <% if item['status'] == 'default' %> | ||||||
|  |                   <span class="badge badge-lg badge-info badge-outline"> | ||||||
|  |                     <%= item['status'] %> | ||||||
|  |                   </span> | ||||||
|  |                 <% else %> | ||||||
|  |                   <%= button_to settings_esign_path, method: :put, params: { name: item['name'] }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %> | ||||||
|  |                     Make Default | ||||||
|  |                   <% end %> | ||||||
|  |                 <% end %> | ||||||
|  |               </td> | ||||||
|  |               <td> | ||||||
|  |                 <% if item['name'] != EsignSettingsController::DEFAULT_CERT_NAME && item['status'] != 'default' %> | ||||||
|  |                   <%= button_to settings_esign_path, params: { name: item['name'] }, method: :delete, class: 'btn btn-outline btn-error btn-xs', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %> | ||||||
|  |                     Remove | ||||||
|  |                   <% end %> | ||||||
|  |                 <% end %> | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |           <% end %> | ||||||
|  |         </tbody> | ||||||
|  |       </table> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
					Loading…
					
					
				
		Reference in new issue