···6868 service_data.each do |x|
6969 id, type, endpoint = x.values_at('id', 'type', 'serviceEndpoint')
70707171- if id.is_a?(String) && id.start_with?('#') && type.is_a?(String) && endpoint.is_a?(String)
7171+ raise FormatError, "Missing service id" unless id
7272+ raise FormatError, "Invalid service id: #{id.inspect}" unless id.is_a?(String)
7373+ next if !id.start_with?('#')
7474+7575+ raise FormatError, "Missing service type" unless type
7676+ raise FormatError, "Invalid service type: #{type.inspect}" unless type.is_a?(String)
7777+7878+ raise FormatError, "Missing service endpoint" unless endpoint
7979+ raise FormatError, "Invalid service endpoint: #{endpoint.inspect}" unless endpoint.is_a?(String)
8080+8181+ begin
7282 @services << ServiceRecord.new(id.gsub(/^#/, ''), type, endpoint)
8383+ rescue FormatError => e
8484+ # ignore services with invalid URIs
7385 end
7486 end
7587 end
+21-9
lib/didkit/plc_operation.rb
···7777 @type = type.to_sym
7878 return unless @type == :plc_operation
79798080- services = operation['services']
8181- raise FormatError, "Missing services key: #{json}" if services.nil?
8282- raise FormatError, "Invalid services data: #{services.inspect}" unless services.is_a?(Hash)
8080+ raise FormatError, "Missing services key: #{json}" if operation['services'].nil?
8181+8282+ parse_services(operation['services'])
8383+ parse_also_known_as(operation['alsoKnownAs'])
8484+ end
8585+8686+ private
83878484- @services = services.map { |k, x|
8585- type, endpoint = x.values_at('type', 'endpoint')
8888+ def parse_services(service_data)
8989+ raise FormatError, "Invalid services data: #{service_data.inspect}" unless service_data.is_a?(Hash)
9090+9191+ @services = []
9292+9393+ service_data.each do |key, data|
9494+ type, endpoint = data.values_at('type', 'endpoint')
86958796 raise FormatError, "Missing service type" unless type
8897 raise FormatError, "Invalid service type: #{type.inspect}" unless type.is_a?(String)
9898+8999 raise FormatError, "Missing service endpoint" unless endpoint
90100 raise FormatError, "Invalid service endpoint: #{endpoint.inspect}" unless endpoint.is_a?(String)
911019292- ServiceRecord.new(k, type, endpoint)
9393- }
9494-9595- parse_also_known_as(operation['alsoKnownAs'])
102102+ begin
103103+ @services << ServiceRecord.new(key, type, endpoint)
104104+ rescue FormatError => e
105105+ # ignore services with invalid URIs
106106+ end
107107+ end
96108 end
97109 end
98110end
+5
lib/didkit/service_record.rb
···2626 # @param key [String] service identifier (without `#`)
2727 # @param type [String] service type
2828 # @param endpoint [String] service endpoint URL
2929+ # @raise [ArgumentError] when the id starts with a `#`
2930 # @raise [FormatError] when the endpoint is not a valid URI
30313132 def initialize(key, type, endpoint)
···3334 uri = URI(endpoint)
3435 rescue URI::Error
3536 raise FormatError, "Invalid service endpoint: #{endpoint.inspect}"
3737+ end
3838+3939+ if key.start_with?('#')
4040+ raise ArgumentError, "Unexpected # in service id"
3641 end
37423843 @key = key
+88-26
spec/document_spec.rb
···3939 let(:json) { base_json.dup.tap { |h| h.delete('id') }}
40404141 it 'should raise a format error' do
4242- expect {
4343- subject.new(did, json)
4444- }.to raise_error(DIDKit::FormatError)
4242+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
4543 end
4644 end
4745···4947 let(:json) { base_json.merge('id' => 123) }
50485149 it 'should raise a format error' do
5252- expect {
5353- subject.new(did, json)
5454- }.to raise_error(DIDKit::FormatError)
5050+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
5551 end
5652 end
5753···5955 let(:json) { base_json.merge('id' => 'did:plc:notmatching') }
60566157 it 'should raise a format error' do
6262- expect {
6363- subject.new(did, json)
6464- }.to raise_error(DIDKit::FormatError)
5858+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
6559 end
6660 end
67616862 context 'when alsoKnownAs is not an array' do
6963 let(:json) { base_json.merge('alsoKnownAs' => 'at://dholms.xyz') }
70647171- it 'should raise an AtHandles format error' do
7272- expect {
7373- subject.new(did, json)
7474- }.to raise_error(DIDKit::FormatError)
6565+ it 'should raise a format error' do
6666+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
7567 end
7668 end
77697870 context 'when alsoKnownAs elements are not strings' do
7971 let(:json) { base_json.merge('alsoKnownAs' => [666]) }
80728181- it 'should raise an AtHandles format error' do
8282- expect {
8383- subject.new(did, json)
8484- }.to raise_error(DIDKit::FormatError)
7373+ it 'should raise a format error' do
7474+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
8575 end
8676 end
8777···9787 it 'should pick those starting with at:// and remove the prefixes' do
9888 doc = subject.new(did, json)
9989 doc.handles.should == ['dholms.xyz', 'other.handle']
9090+ end
9191+9292+ it 'should return all entries in #also_known_as' do
9393+ op = subject.new(did, json)
9494+ op.also_known_as.should == ['at://dholms.xyz', 'https://example.com', 'at://other.handle']
10095 end
10196 end
10297···10499 let(:json) { base_json.merge('service' => 'not-an-array') }
105100106101 it 'should raise a format error' do
107107- expect {
108108- subject.new(did, json)
109109- }.to raise_error(DIDKit::FormatError)
102102+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
110103 end
111104 end
112105···114107 let(:json) { base_json.merge('service' => ['invalid']) }
115108116109 it 'should raise a format error' do
117117- expect {
118118- subject.new(did, json)
119119- }.to raise_error(DIDKit::FormatError)
110110+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
111111+ end
112112+ end
113113+114114+ context 'when service entry id is missing' do
115115+ let(:json) { base_json.merge('service' => [service]) }
116116+ let(:service) {{ 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'https://pds.dholms.xyz' }}
117117+118118+ it 'should raise a format error' do
119119+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
120120+ end
121121+ end
122122+123123+ context 'when service entry id is not a string' do
124124+ let(:json) { base_json.merge('service' => [service]) }
125125+ let(:service) {{ 'id' => 5, 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'https://pds.dholms.xyz' }}
126126+127127+ it 'should raise a format error' do
128128+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
129129+ end
130130+ end
131131+132132+ context 'when service entry id does not start with a #' do
133133+ let(:json) { base_json.merge('service' => [service]) }
134134+ let(:service) {{ 'id' => 'atproto_pds', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'https://pds.dholms.xyz' }}
135135+136136+ it 'should *not* raise a format error' do
137137+ expect { subject.new(did, json) }.to_not raise_error
138138+ end
139139+ end
140140+141141+ context 'when service entry type is missing' do
142142+ let(:json) { base_json.merge('service' => [service]) }
143143+ let(:service) {{ 'id' => '#atproto_pds', 'serviceEndpoint' => 'https://pds.dholms.xyz' }}
144144+145145+ it 'should raise a format error' do
146146+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
147147+ end
148148+ end
149149+150150+ context 'when service entry type is not a string' do
151151+ let(:json) { base_json.merge('service' => [service]) }
152152+ let(:service) {{ 'id' => '#atproto_pds', 'type' => 7, 'serviceEndpoint' => 'https://pds.dholms.xyz' }}
153153+154154+ it 'should raise a format error' do
155155+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
156156+ end
157157+ end
158158+159159+ context 'when service entry endpoint is missing' do
160160+ let(:json) { base_json.merge('service' => [service]) }
161161+ let(:service) {{ 'id' => '#atproto_pds', 'type' => 'AtprotoPersonalDataServer' }}
162162+163163+ it 'should raise a format error' do
164164+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
165165+ end
166166+ end
167167+168168+ context 'when service entry endpoint is not a string' do
169169+ let(:json) { base_json.merge('service' => [service]) }
170170+ let(:service) {{ 'id' => '#atproto_pds', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => :somewhere }}
171171+172172+ it 'should raise a format error' do
173173+ expect { subject.new(did, json) }.to raise_error(DIDKit::FormatError)
174174+ end
175175+ end
176176+177177+ context 'when service entry endpoint is not an URI' do
178178+ let(:json) { base_json.merge('service' => [service]) }
179179+ let(:service) {{ 'id' => '#atproto_pds', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => '<script>alert()</script>' }}
180180+181181+ it 'should *not* raise a format error' do
182182+ expect { subject.new(did, json) }.to_not raise_error
120183 end
121184 end
122185···124187 let(:services) {
125188 [
126189 { 'id' => '#atproto_pds', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'https://pds.dholms.xyz' },
127127- { 'id' => 'not_a_hash', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'https://pds.dholms.xyz' },
128128- { 'id' => '#wrong_type', 'type' => 123, 'serviceEndpoint' => 'https://pds.dholms.xyz' },
129129- { 'id' => '#wrong_endpoint', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 123 },
190190+ { 'id' => 'missing_hash', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'https://pds.dholms.xyz' },
191191+ { 'id' => '#wrong_endpoint', 'type' => 'AtprotoPersonalDataServer', 'serviceEndpoint' => 'this is not a url' },
130192 { 'id' => '#lycan', 'type' => 'LycanService', 'serviceEndpoint' => 'https://lycan.feeds.blue' }
131193 ]
132194 }
+91-16
spec/plc_operation_spec.rb
···157157 context 'when alsoKnownAs is not an array' do
158158 let(:json) { base_json.tap { |h| h['operation']['alsoKnownAs'] = 'at://dholms.xyz' }}
159159160160- it 'should raise an AtHandles format error' do
161161- expect {
162162- subject.new(json)
163163- }.to raise_error(DIDKit::FormatError)
160160+ it 'should raise a format error' do
161161+ expect { subject.new(json) }.to raise_error(DIDKit::FormatError)
164162 end
165163 end
166164167165 context 'when alsoKnownAs elements are not strings' do
168166 let(:json) { base_json.tap { |h| h['operation']['alsoKnownAs'] = [666] }}
169167170170- it 'should raise an AtHandles format error' do
171171- expect {
172172- subject.new(json)
173173- }.to raise_error(DIDKit::FormatError)
168168+ it 'should raise a format error' do
169169+ expect { subject.new(json) }.to raise_error(DIDKit::FormatError)
174170 end
175171 end
176172···189185 op = subject.new(json)
190186 op.handles.should == ['dholms.xyz', 'other.handle']
191187 end
188188+189189+ it 'should return all entries in #also_known_as' do
190190+ op = subject.new(json)
191191+ op.also_known_as.should == ['at://dholms.xyz', 'https://example.com', 'at://other.handle']
192192+ end
192193 end
193194194195 context 'when services are missing' do
···217218 end
218219 end
219220220220- context 'when a service entry is missing fields' do
221221+ context 'when a service entry type is missing' do
222222+ let(:json) {
223223+ base_json.tap { |h|
224224+ h['operation']['services'] = {
225225+ "atproto_pds" => { "endpoint" => "https://pds.dholms.xyz" }
226226+ }
227227+ }
228228+ }
229229+230230+ it 'should raise a format error' do
231231+ expect { subject.new(json) }.to raise_error(DIDKit::FormatError)
232232+ end
233233+ end
234234+235235+ context 'when a service entry type is not a string' do
236236+ let(:json) {
237237+ base_json.tap { |h|
238238+ h['operation']['services'] = {
239239+ "atproto_pds" => { "type" => 77, "endpoint" => "https://pds.dholms.xyz" }
240240+ }
241241+ }
242242+ }
243243+244244+ it 'should raise a format error' do
245245+ expect { subject.new(json) }.to raise_error(DIDKit::FormatError)
246246+ end
247247+ end
248248+249249+ context 'when a service entry endpoint is missing' do
250250+ let(:json) {
251251+ base_json.tap { |h|
252252+ h['operation']['services'] = {
253253+ "atproto_pds" => { "type" => "AtprotoPersonalDataServer" }
254254+ }
255255+ }
256256+ }
257257+258258+ it 'should raise a format error' do
259259+ expect { subject.new(json) }.to raise_error(DIDKit::FormatError)
260260+ end
261261+ end
262262+263263+ context 'when a service entry endpoint is not a string' do
221264 let(:json) {
222265 base_json.tap { |h|
223266 h['operation']['services'] = {
224224- "atproto_pds" => {
225225- "endpoint" => "https://pds.dholms.xyz"
226226- },
227227- "atproto_labeler" => {
228228- "type" => "AtprotoLabeler",
229229- "endpoint" => "https://labeler.example.com"
230230- }
267267+ "atproto_pds" => { "type" => "AtprotoPersonalDataServer", "endpoint" => { :host => 'localhost' }}
231268 }
232269 }
233270 }
234271235272 it 'should raise a format error' do
236273 expect { subject.new(json) }.to raise_error(DIDKit::FormatError)
274274+ end
275275+ end
276276+277277+ context 'when a service entry endpoint is not an URI' do
278278+ let(:json) {
279279+ base_json.tap { |h|
280280+ h['operation']['services'] = {
281281+ "atproto_pds" => { "type" => "AtprotoPersonalDataServer", "endpoint" => "2 + 2" }
282282+ }
283283+ }
284284+ }
285285+286286+ it 'should *not* raise a format error' do
287287+ expect { subject.new(json) }.to_not raise_error
237288 end
238289 end
239290···360411 op.labeler_host.should be_nil
361412 op.labeller_host.should be_nil
362413 end
414414+ end
415415+ end
416416+417417+ context "when some services have endpoints that aren't valid URIs" do
418418+ let(:json) {
419419+ base_json.tap { |h|
420420+ h['operation']['services'] = {
421421+ "atproto_labeler" => {
422422+ "type" => "AtprotoLabeler",
423423+ "endpoint" => "bla bla bla"
424424+ },
425425+ "custom_service" => {
426426+ "type" => "OtherService",
427427+ "endpoint" => "https://custom.example.com"
428428+ }
429429+ }
430430+ }
431431+ }
432432+433433+ it 'should only return valid services' do
434434+ op = subject.new(json)
435435+436436+ op.services.length.should == 1
437437+ op.services[0].type == 'OtherService'
363438 end
364439 end
365440 end