claim_service.rb 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. # frozen_string_literal: true
  2. class Keys::ClaimService < BaseService
  3. HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
  4. class Result < ActiveModelSerializers::Model
  5. attributes :account, :device_id, :key_id,
  6. :key, :signature
  7. def initialize(account, device_id, key_attributes = {})
  8. super(
  9. account: account,
  10. device_id: device_id,
  11. key_id: key_attributes[:key_id],
  12. key: key_attributes[:key],
  13. signature: key_attributes[:signature],
  14. )
  15. end
  16. end
  17. def call(source_account, target_account_id, device_id)
  18. @source_account = source_account
  19. @target_account = Account.find(target_account_id)
  20. @device_id = device_id
  21. if @target_account.local?
  22. claim_local_key!
  23. else
  24. claim_remote_key!
  25. end
  26. rescue ActiveRecord::RecordNotFound
  27. nil
  28. end
  29. private
  30. def claim_local_key!
  31. device = @target_account.devices.find_by(device_id: @device_id)
  32. key = nil
  33. ApplicationRecord.transaction do
  34. key = device.one_time_keys.order(Arel.sql('random()')).first!
  35. key.destroy!
  36. end
  37. @result = Result.new(@target_account, @device_id, key)
  38. end
  39. def claim_remote_key!
  40. query_result = QueryService.new.call(@target_account)
  41. device = query_result.find(@device_id)
  42. return unless device.present? && device.valid_claim_url?
  43. json = fetch_resource_with_post(device.claim_url)
  44. return unless json.present? && json['publicKeyBase64'].present?
  45. @result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue'))
  46. rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e
  47. Rails.logger.debug { "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" }
  48. nil
  49. end
  50. def fetch_resource_with_post(uri)
  51. build_post_request(uri).perform do |response|
  52. raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
  53. body_to_json(response.body_with_limit) if response.code == 200
  54. end
  55. end
  56. def build_post_request(uri)
  57. Request.new(:post, uri).tap do |request|
  58. request.on_behalf_of(@source_account)
  59. request.add_headers(HEADERS)
  60. end
  61. end
  62. end