78 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
		
		
			
		
	
	
			78 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| 
								 | 
							
								# frozen_string_literal: true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Keys::ClaimService < BaseService
							 | 
						||
| 
								 | 
							
								  HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class Result < ActiveModelSerializers::Model
							 | 
						||
| 
								 | 
							
								    attributes :account, :device_id, :key_id,
							 | 
						||
| 
								 | 
							
								               :key, :signature
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def initialize(account, device_id, key_attributes = {})
							 | 
						||
| 
								 | 
							
								      @account   = account
							 | 
						||
| 
								 | 
							
								      @device_id = device_id
							 | 
						||
| 
								 | 
							
								      @key_id    = key_attributes[:key_id]
							 | 
						||
| 
								 | 
							
								      @key       = key_attributes[:key]
							 | 
						||
| 
								 | 
							
								      @signature = key_attributes[:signature]
							 | 
						||
| 
								 | 
							
								    end
							 | 
						||
| 
								 | 
							
								  end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def call(source_account, target_account_id, device_id)
							 | 
						||
| 
								 | 
							
								    @source_account = source_account
							 | 
						||
| 
								 | 
							
								    @target_account = Account.find(target_account_id)
							 | 
						||
| 
								 | 
							
								    @device_id      = device_id
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if @target_account.local?
							 | 
						||
| 
								 | 
							
								      claim_local_key!
							 | 
						||
| 
								 | 
							
								    else
							 | 
						||
| 
								 | 
							
								      claim_remote_key!
							 | 
						||
| 
								 | 
							
								    end
							 | 
						||
| 
								 | 
							
								  rescue ActiveRecord::RecordNotFound
							 | 
						||
| 
								 | 
							
								    nil
							 | 
						||
| 
								 | 
							
								  end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def claim_local_key!
							 | 
						||
| 
								 | 
							
								    device = @target_account.devices.find_by(device_id: @device_id)
							 | 
						||
| 
								 | 
							
								    key    = nil
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ApplicationRecord.transaction do
							 | 
						||
| 
								 | 
							
								      key = device.one_time_keys.order(Arel.sql('random()')).first!
							 | 
						||
| 
								 | 
							
								      key.destroy!
							 | 
						||
| 
								 | 
							
								    end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @result = Result.new(@target_account, @device_id, key)
							 | 
						||
| 
								 | 
							
								  end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def claim_remote_key!
							 | 
						||
| 
								 | 
							
								    query_result = QueryService.new.call(@target_account)
							 | 
						||
| 
								 | 
							
								    device       = query_result.find(@device_id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return unless device.present? && device.valid_claim_url?
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    json = fetch_resource_with_post(device.claim_url)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return unless json.present? && json['publicKeyBase64'].present?
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue'))
							 | 
						||
| 
								 | 
							
								  rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e
							 | 
						||
| 
								 | 
							
								    Rails.logger.debug "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}"
							 | 
						||
| 
								 | 
							
								    nil
							 | 
						||
| 
								 | 
							
								  end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def fetch_resource_with_post(uri)
							 | 
						||
| 
								 | 
							
								    build_post_request(uri).perform do |response|
							 | 
						||
| 
								 | 
							
								      raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      body_to_json(response.body_with_limit) if response.code == 200
							 | 
						||
| 
								 | 
							
								    end
							 | 
						||
| 
								 | 
							
								  end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def build_post_request(uri)
							 | 
						||
| 
								 | 
							
								    Request.new(:post, uri).tap do |request|
							 | 
						||
| 
								 | 
							
								      request.on_behalf_of(@source_account, :uri)
							 | 
						||
| 
								 | 
							
								      request.add_headers(HEADERS)
							 | 
						||
| 
								 | 
							
								    end
							 | 
						||
| 
								 | 
							
								  end
							 | 
						||
| 
								 | 
							
								end
							 |