forked from cybrespace/mastodon
		
	Support Actors/Statuses with multiple types (#7305)
* Add equals_or_includes_any? helper in JsonLdHelper * Support arrays in JSON-LD type fields for actors/tags/objects. * Spec for resolving accounts with extension types * Style tweaks for codeclimate
This commit is contained in:
		
							parent
							
								
									86efccce2a
								
							
						
					
					
						commit
						dc786c0cf4
					
				
					 12 changed files with 43 additions and 24 deletions
				
			
		| 
						 | 
					@ -5,6 +5,10 @@ module JsonLdHelper
 | 
				
			||||||
    haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
 | 
					    haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def equals_or_includes_any?(haystack, needles)
 | 
				
			||||||
 | 
					    needles.any? { |needle| equals_or_includes?(haystack, needle) }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def first_of_value(value)
 | 
					  def first_of_value(value)
 | 
				
			||||||
    value.is_a?(Array) ? value.first : value
 | 
					    value.is_a?(Array) ? value.first : value
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,12 +61,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
    return if @object['tag'].nil?
 | 
					    return if @object['tag'].nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    as_array(@object['tag']).each do |tag|
 | 
					    as_array(@object['tag']).each do |tag|
 | 
				
			||||||
      case tag['type']
 | 
					      if equals_or_includes?(tag['type'], 'Hashtag')
 | 
				
			||||||
      when 'Hashtag'
 | 
					 | 
				
			||||||
        process_hashtag tag, status
 | 
					        process_hashtag tag, status
 | 
				
			||||||
      when 'Mention'
 | 
					      elsif equals_or_includes?(tag['type'], 'Mention')
 | 
				
			||||||
        process_mention tag, status
 | 
					        process_mention tag, status
 | 
				
			||||||
      when 'Emoji'
 | 
					      elsif equals_or_includes?(tag['type'], 'Emoji')
 | 
				
			||||||
        process_emoji tag, status
 | 
					        process_emoji tag, status
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -235,11 +234,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def supported_object_type?
 | 
					  def supported_object_type?
 | 
				
			||||||
    SUPPORTED_TYPES.include?(@object['type'])
 | 
					    equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def converted_object_type?
 | 
					  def converted_object_type?
 | 
				
			||||||
    CONVERTED_TYPES.include?(@object['type'])
 | 
					    equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def skip_download?
 | 
					  def skip_download?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ActivityPub::Activity::Update < ActivityPub::Activity
 | 
					class ActivityPub::Activity::Update < ActivityPub::Activity
 | 
				
			||||||
  def perform
 | 
					  def perform
 | 
				
			||||||
    case @object['type']
 | 
					    update_account if equals_or_includes?(@object['type'], 'Person')
 | 
				
			||||||
    when 'Person'
 | 
					 | 
				
			||||||
      update_account
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,6 +56,6 @@ class ActivityPub::FetchRemoteAccountService < BaseService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def expected_type?
 | 
					  def expected_type?
 | 
				
			||||||
    SUPPORTED_TYPES.include?(@json['type'])
 | 
					    equals_or_includes_any?(@json['type'], SUPPORTED_TYPES)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def person?
 | 
					  def person?
 | 
				
			||||||
    ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@json['type'])
 | 
					    equals_or_includes_any?(@json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def public_key?
 | 
					  def public_key?
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,6 @@ class ActivityPub::FetchRemoteKeyService < BaseService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def confirmed_owner?
 | 
					  def confirmed_owner?
 | 
				
			||||||
    ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@owner['type']) && value_or_id(@owner['publicKey']) == @json['id']
 | 
					    equals_or_includes_any?(@owner['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && value_or_id(@owner['publicKey']) == @json['id']
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def expected_type?
 | 
					  def expected_type?
 | 
				
			||||||
    (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? @json['type']
 | 
					    equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def needs_update(actor)
 | 
					  def needs_update(actor)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -201,10 +201,7 @@ class ActivityPub::ProcessAccountService < BaseService
 | 
				
			||||||
    return if @json['tag'].blank?
 | 
					    return if @json['tag'].blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    as_array(@json['tag']).each do |tag|
 | 
					    as_array(@json['tag']).each do |tag|
 | 
				
			||||||
      case tag['type']
 | 
					      process_emoji tag if equals_or_includes?(tag['type'], 'Emoji')
 | 
				
			||||||
      when 'Emoji'
 | 
					 | 
				
			||||||
        process_emoji tag
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ class FetchAtomService < BaseService
 | 
				
			||||||
    elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type)
 | 
					    elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type)
 | 
				
			||||||
      body = response.body_with_limit
 | 
					      body = response.body_with_limit
 | 
				
			||||||
      json = body_to_json(body)
 | 
					      json = body_to_json(body)
 | 
				
			||||||
      if supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) && json['inbox'].present?
 | 
					      if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present?
 | 
				
			||||||
        [json['id'], { prefetched_body: body, id: true }, :activitypub]
 | 
					        [json['id'], { prefetched_body: body, id: true }, :activitypub]
 | 
				
			||||||
      elsif supported_context?(json) && expected_type?(json)
 | 
					      elsif supported_context?(json) && expected_type?(json)
 | 
				
			||||||
        [json['id'], { prefetched_body: body, id: true }, :activitypub]
 | 
					        [json['id'], { prefetched_body: body, id: true }, :activitypub]
 | 
				
			||||||
| 
						 | 
					@ -62,7 +62,7 @@ class FetchAtomService < BaseService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def expected_type?(json)
 | 
					  def expected_type?(json)
 | 
				
			||||||
    (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? json['type']
 | 
					    equals_or_includes_any?(json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def process_html(response)
 | 
					  def process_html(response)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -189,7 +189,7 @@ class ResolveAccountService < BaseService
 | 
				
			||||||
    return @actor_json if defined?(@actor_json)
 | 
					    return @actor_json if defined?(@actor_json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    json        = fetch_resource(actor_url, false)
 | 
					    json        = fetch_resource(actor_url, false)
 | 
				
			||||||
    @actor_json = supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) ? json : nil
 | 
					    @actor_json = supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) ? json : nil
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def atom
 | 
					  def atom
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,10 +16,9 @@ class ResolveURLService < BaseService
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def process_url
 | 
					  def process_url
 | 
				
			||||||
    case type
 | 
					    if equals_or_includes_any?(type, %w(Application Group Organization Person Service))
 | 
				
			||||||
    when 'Application', 'Group', 'Organization', 'Person', 'Service'
 | 
					 | 
				
			||||||
      FetchRemoteAccountService.new.call(atom_url, body, protocol)
 | 
					      FetchRemoteAccountService.new.call(atom_url, body, protocol)
 | 
				
			||||||
    when 'Note', 'Article', 'Image', 'Video'
 | 
					    elsif equals_or_includes_any?(type, %w(Note Article Image Video))
 | 
				
			||||||
      FetchRemoteStatusService.new.call(atom_url, body, protocol)
 | 
					      FetchRemoteStatusService.new.call(atom_url, body, protocol)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								spec/fixtures/requests/activitypub-actor-individual.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								spec/fixtures/requests/activitypub-actor-individual.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					HTTP/1.1 200 OK
 | 
				
			||||||
 | 
					Cache-Control: max-age=0, private, must-revalidate
 | 
				
			||||||
 | 
					Content-Type: application/activity+json; charset=utf-8
 | 
				
			||||||
 | 
					Link: <https://ap.example.com/.well-known/webfinger?resource=acct%3Afoo%40ap.example.com>; rel="lrdd"; type="application/xrd+xml", <https://ap.example.com/users/foo.atom>; rel="alternate"; type="application/atom+xml", <https://ap.example.com/users/foo>; rel="alternate"; type="application/activity+json"
 | 
				
			||||||
 | 
					Vary: Accept-Encoding
 | 
				
			||||||
 | 
					X-Content-Type-Options: nosniff
 | 
				
			||||||
 | 
					X-Xss-Protection: 1; mode=block
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"vcard": "http://www.w3.org/2006/vcard/ns#"},{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation"}],"id":"https://ap.example.com/users/foo","type":["Person","vcard:individual"],"following":"https://ap.example.com/users/foo/following","followers":"https://ap.example.com/users/foo/followers","inbox":"https://ap.example.com/users/foo/inbox","outbox":"https://ap.example.com/users/foo/outbox","preferredUsername":"foo","vcard:fn":"foo","name":"","summary":"\u003cp\u003etest\u003c/p\u003e","url":"https://ap.example.com/@foo","manuallyApprovesFollowers":false,"publicKey":{"id":"https://ap.example.com/users/foo#main-key","owner":"https://ap.example.com/users/foo","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39\n4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S\n6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh\n8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP\nahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW\npva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu\nHQIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"https://ap.example.com/inbox"},"icon":{"type":"Image","url":"https://quitter.no/avatar/7477-300-20160211190340.png"}}
 | 
				
			||||||
| 
						 | 
					@ -105,6 +105,20 @@ RSpec.describe ResolveAccountService do
 | 
				
			||||||
      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
 | 
					      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'with multiple types' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor-individual.txt'))
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns new remote account' do
 | 
				
			||||||
 | 
					        account = subject.call('foo@ap.example.com')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(account.activitypub?).to eq true
 | 
				
			||||||
 | 
					        expect(account.domain).to eq 'ap.example.com'
 | 
				
			||||||
 | 
					        expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pending
 | 
					    pending
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue