aaf-mdqt 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +70 -0
  3. data/.github/workflows/ruby.yml +41 -0
  4. data/.gitignore +25 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +1 -0
  7. data/.rubocop_todo.yml +296 -0
  8. data/.ruby-version +1 -0
  9. data/.tool-versions +1 -0
  10. data/.travis.yml +7 -0
  11. data/CHANGELOG.md +168 -0
  12. data/CODE_OF_CONDUCT.md +74 -0
  13. data/Gemfile +9 -0
  14. data/LICENSE.txt +21 -0
  15. data/Makefile +4 -0
  16. data/README.md +268 -0
  17. data/Rakefile +5 -0
  18. data/aaf-mdqt.gemspec +46 -0
  19. data/bin/console +14 -0
  20. data/bin/setup +8 -0
  21. data/cucumber.yml +2 -0
  22. data/exe/mdqt +174 -0
  23. data/lib/mdqt/cli/base.rb +190 -0
  24. data/lib/mdqt/cli/cache_control.rb +25 -0
  25. data/lib/mdqt/cli/check.rb +78 -0
  26. data/lib/mdqt/cli/compliance.rb +0 -0
  27. data/lib/mdqt/cli/defaults.rb +70 -0
  28. data/lib/mdqt/cli/entities.rb +47 -0
  29. data/lib/mdqt/cli/exists.rb +0 -0
  30. data/lib/mdqt/cli/get.rb +130 -0
  31. data/lib/mdqt/cli/list.rb +65 -0
  32. data/lib/mdqt/cli/ln.rb +81 -0
  33. data/lib/mdqt/cli/ls.rb +54 -0
  34. data/lib/mdqt/cli/rename.rb +75 -0
  35. data/lib/mdqt/cli/reset.rb +27 -0
  36. data/lib/mdqt/cli/services.rb +25 -0
  37. data/lib/mdqt/cli/transform.rb +33 -0
  38. data/lib/mdqt/cli/url.rb +37 -0
  39. data/lib/mdqt/cli/version.rb +17 -0
  40. data/lib/mdqt/cli.rb +24 -0
  41. data/lib/mdqt/client/identifier_utils.rb +51 -0
  42. data/lib/mdqt/client/metadata_file.rb +144 -0
  43. data/lib/mdqt/client/metadata_response.rb +182 -0
  44. data/lib/mdqt/client/metadata_service.rb +194 -0
  45. data/lib/mdqt/client/metadata_validator.rb +81 -0
  46. data/lib/mdqt/client.rb +83 -0
  47. data/lib/mdqt/schema/MetadataExchange.xsd +112 -0
  48. data/lib/mdqt/schema/mdqt_check_schema.xsd +5 -0
  49. data/lib/mdqt/schema/oasis-200401-wss-wssecurity-secext-1.0.xsd +195 -0
  50. data/lib/mdqt/schema/oasis-200401-wss-wssecurity-utility-1.0.xsd +108 -0
  51. data/lib/mdqt/schema/saml-schema-assertion-2.0.xsd +283 -0
  52. data/lib/mdqt/schema/saml-schema-metadata-2.0.xsd +337 -0
  53. data/lib/mdqt/schema/ws-addr.xsd +137 -0
  54. data/lib/mdqt/schema/ws-authorization.xsd +145 -0
  55. data/lib/mdqt/schema/ws-federation.xsd +471 -0
  56. data/lib/mdqt/schema/ws-securitypolicy-1.2.xsd +1205 -0
  57. data/lib/mdqt/schema/xenc-schema.xsd +136 -0
  58. data/lib/mdqt/schema/xml.xsd +287 -0
  59. data/lib/mdqt/schema/xmldsig-core-schema.xsd +309 -0
  60. data/lib/mdqt/version.rb +3 -0
  61. data/lib/mdqt.rb +5 -0
  62. data/lib/tasks/cucumber.rake +8 -0
  63. data/lib/tasks/spec.rake +5 -0
  64. data/lib/tasks/tests.rake +6 -0
  65. data/lib/tasks/yard.rake +6 -0
  66. metadata +332 -0
@@ -0,0 +1,54 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+
7
+ class Ls < Base
8
+
9
+ def run
10
+
11
+ options.validate = true
12
+
13
+ advise_on_xml_signing_support
14
+ halt!("Cannot check a metadata file without XML support: please install additional gems") unless MDQT::Client.verification_available?
15
+
16
+ client = MDQT::Client.new(
17
+ options.service,
18
+ verbose: options.verbose,
19
+ explain: options.explain ? true : false,
20
+ )
21
+
22
+ results = []
23
+
24
+ p_args = args.empty? ? Dir.glob("*.xml") : args
25
+
26
+ p_args.each do |filename|
27
+
28
+ file = client.open_metadata(filename)
29
+
30
+ halt!("Cannot access file #{filename}") unless file.readable?
31
+
32
+ #halt!("File #{filename} is a metadata aggregate, cannot create entityID hashed link!") if file.aggregate?
33
+ next if file.aggregate?
34
+ halt!("XML validation failed for #{filename}:\n#{file.validation_error}") unless file.valid?
35
+
36
+ halt!("Cannot find entityID for #{filename}") unless file.entity_id
37
+
38
+ results << {id: file.entity_id, type: file.type, filename: file.basename}
39
+
40
+ end
41
+
42
+ results.sort_by { | r | [r[:id], r[:type]] }.each {|r| puts "#{r[:id]}, #{r[:type]}, #{r[:filename]}" }
43
+
44
+ end
45
+
46
+ end
47
+
48
+ private
49
+
50
+ end
51
+
52
+ end
53
+
54
+
@@ -0,0 +1,75 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+
7
+ class Rename < Base
8
+
9
+ def run
10
+
11
+ options.validate = true
12
+
13
+ advise_on_xml_signing_support
14
+ halt!("Cannot check a metadata file without XML support: please install additional gems") unless MDQT::Client.verification_available?
15
+
16
+ client = MDQT::Client.new(
17
+ options.service,
18
+ verbose: options.verbose,
19
+ explain: options.explain ? true : false,
20
+ )
21
+
22
+ halt!("Please specify a file to rename!") if args.empty?
23
+
24
+ args.each do |filename|
25
+
26
+ next if File.symlink?(filename)
27
+
28
+ file = client.open_metadata(filename)
29
+
30
+ halt!("Cannot access file #{filename}") unless file.readable?
31
+ halt!("File #{filename} is a metadata aggregate, cannot rename to hashed entityID!") if file.aggregate?
32
+ halt!("XML validation failed for #{filename}:\n#{file.validation_error}") unless file.valid?
33
+
34
+ halt!("Cannot find entityID for #{filename}") unless file.entity_id
35
+
36
+ newname = file.linkname # Using the same name as the link, not super-obvious
37
+ next if filename == newname
38
+
39
+ if file.turd?
40
+ hey "Warning: will not process backup/turd files"
41
+ next
42
+ end
43
+
44
+ message = ""
45
+
46
+ if File.exist?(newname)
47
+ if options.force
48
+ File.delete(newname)
49
+ else
50
+ halt!("Cannot rename #{filename} to #{newname} - File exists! Use --force to override")
51
+ next
52
+ end
53
+ end
54
+
55
+ File.rename(filename, newname)
56
+
57
+ if options.link
58
+ File.delete(filename) if options.force && File.exist?(filename)
59
+ File.symlink(newname, filename) unless newname == filename
60
+ end
61
+
62
+ hey("#{filename} renamed to #{newname} [#{file.entity_id}] #{message}") if options.verbose
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
69
+ private
70
+
71
+ end
72
+
73
+ end
74
+
75
+
@@ -0,0 +1,27 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+
7
+ class Reset < Base
8
+
9
+ def run
10
+
11
+ client = MDQT::Client.new(
12
+ :not_required,
13
+ verbose: false,
14
+ cache_type: :file
15
+ )
16
+
17
+ print "Removing all cached files... "
18
+ client.cache_reset!
19
+ yay "done."
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,25 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+
7
+ class Services < Base
8
+
9
+ def run
10
+
11
+ puts "\nKnown services:"
12
+ puts
13
+ MDQT::CLI::Defaults.services.each do |service|
14
+ puts "#{service[:alias]}: #{service[:url]}"
15
+ end
16
+ puts
17
+ puts "Specify these in commands using the --service option or MDQ_BASE_URL environment variable"
18
+ puts
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+
@@ -0,0 +1,33 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+
7
+ class Transform < Base
8
+
9
+ IdentifierUtils = MDQT::Client::IdentifierUtils
10
+
11
+ def run
12
+
13
+ halt!("No entityIDs have been specified!") if args.empty?
14
+
15
+ args.each do |arg|
16
+ puts transform(arg)
17
+ end
18
+
19
+ end
20
+
21
+ def transform(arg)
22
+ arg = arg.strip
23
+ return arg if IdentifierUtils.valid_transformed?(arg)
24
+ return IdentifierUtils.correct_lazy_transformed(arg) if IdentifierUtils.lazy_transformed?(arg)
25
+ return IdentifierUtils.correct_fish_transformed(arg) if IdentifierUtils.fish_transformed?(arg)
26
+ IdentifierUtils.transform_uri(arg)
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,37 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+ require 'uri'
7
+
8
+ class URL < Base
9
+
10
+ def run
11
+
12
+ mds = MDQT::Client::MetadataService.new(service_url(options),
13
+ verbose:false,
14
+ cache_type: :none,
15
+ explain: false,
16
+ tls_cert_check: false)
17
+
18
+ if args.empty?
19
+ puts service_url(options)
20
+ else
21
+ args.each do |arg|
22
+ puts build_url(mds, arg)
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ def build_url(mds, entity_id)
29
+
30
+ URI.join(service_url(options), "entities/#{mds.prepare_id(entity_id)}")
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,17 @@
1
+ module MDQT
2
+
3
+ class CLI
4
+
5
+ require 'mdqt/cli/base'
6
+
7
+ class Version < Base
8
+
9
+ def run
10
+ puts "MDQT version #{MDQT::VERSION}"
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
17
+
data/lib/mdqt/cli.rb ADDED
@@ -0,0 +1,24 @@
1
+ module MDQT
2
+ class CLI
3
+
4
+ require 'mdqt/client'
5
+
6
+ require 'mdqt/cli/defaults'
7
+ require 'mdqt/cli/cache_control'
8
+
9
+ require 'mdqt/cli/get'
10
+ require 'mdqt/cli/reset'
11
+ require 'mdqt/cli/version'
12
+ require 'mdqt/cli/transform'
13
+ require 'mdqt/cli/check'
14
+ require 'mdqt/cli/entities'
15
+ require 'mdqt/cli/ln'
16
+ require 'mdqt/cli/ls'
17
+ require 'mdqt/cli/list'
18
+ require 'mdqt/cli/services'
19
+ require 'mdqt/cli/rename'
20
+ require 'mdqt/cli/url'
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,51 @@
1
+ module MDQT
2
+ class Client
3
+
4
+ module IdentifierUtils
5
+
6
+ require 'digest'
7
+
8
+ class << self
9
+
10
+ def uri_to_sha1(uri)
11
+ raise "An empty string cannot be transformed" if uri.empty?
12
+ Digest::SHA1.hexdigest(uri.to_s.strip)
13
+ end
14
+
15
+ def transform_uri(uri)
16
+ "{sha1}#{uri_to_sha1(uri)}"
17
+ end
18
+
19
+ def normalize_to_sha1(identifier)
20
+ identifier.start_with?("{sha1}") ?
21
+ identifier.gsub("{sha1}", "") :
22
+ uri_to_sha1(identifier)
23
+ end
24
+
25
+ def valid_transformed?(string)
26
+ (string =~ /^[{]sha1[}][0-9a-f]{40}$/i).nil? ? false : true
27
+ end
28
+
29
+ def lazy_transformed?(string)
30
+ (string =~ /^[\[]sha1[\]][0-9a-f]{40}$/i).nil? ? false : true
31
+ end
32
+
33
+ def fish_transformed?(string)
34
+ (string =~ /^sha1[0-9a-f]{40}$/i).nil? ? false : true
35
+ end
36
+
37
+ def correct_lazy_transformed(lazy)
38
+ lazy.gsub("[sha1]", "{sha1}").downcase
39
+ end
40
+
41
+ def correct_fish_transformed(lazy)
42
+ lazy.gsub("sha1", "{sha1}").downcase
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,144 @@
1
+ module MDQT
2
+ class Client
3
+
4
+ class MetadataFile
5
+
6
+ require 'digest'
7
+
8
+ def initialize(filename, options = {})
9
+ @filename = File.absolute_path(filename)
10
+ @data = nil
11
+ @expires = nil
12
+ @etag = nil
13
+ @last_modified = nil
14
+ @explanation = {}
15
+ end
16
+
17
+ def filename
18
+ @filename
19
+ end
20
+
21
+ def basename
22
+ File.basename(filename)
23
+ end
24
+
25
+ def identifier
26
+ entity_id
27
+ end
28
+
29
+ def entity_id
30
+ raise "Incorrect metadata file type - aggregate" if aggregate?
31
+ @entity_id ||= extract_entity_id
32
+ end
33
+
34
+ def entity_ids
35
+ @entity_ids ||= extract_entity_ids
36
+ end
37
+
38
+ def data
39
+ @data ||= File.read(filename)
40
+ end
41
+
42
+ def readable?
43
+ File.readable?(filename)
44
+ end
45
+
46
+ def turd?
47
+ return true if basename.end_with?("~")
48
+ return true if basename.end_with?(".bak")
49
+ false
50
+ end
51
+
52
+ def type
53
+ @type ||= calculate_type
54
+ end
55
+
56
+ def aggregate?
57
+ type == :aggregate
58
+ end
59
+
60
+ def expires
61
+ @expires
62
+ end
63
+
64
+ def etag
65
+ @etag
66
+ end
67
+
68
+ def last_modified
69
+ @last_modified
70
+ end
71
+
72
+ def ok?
73
+ @ok
74
+ end
75
+
76
+ def signed?
77
+ @data.include? "Signature" # This is... not great
78
+ end
79
+
80
+ def verified_signature?(certs = [], _ = {})
81
+ validator = MetadataValidator.new(certs: [certs].flatten)
82
+ validator.verified_signature?(self)
83
+ end
84
+
85
+ def valid?
86
+ validator = MetadataValidator.new
87
+ validator.valid?(self)
88
+ end
89
+
90
+ def validation_error
91
+ validator = MetadataValidator.new
92
+ validator.validation_error(self)
93
+ end
94
+
95
+ def canonical_filename
96
+ if identifier.empty?
97
+ @filename = "aggregate-#{Digest::SHA1.hexdigest(@service)}.xml"
98
+ else
99
+ @filename ||= identifier.start_with?("{sha1}") ?
100
+ "#{@identifier.gsub("{sha1}", "")}.xml" :
101
+ "#{Digest::SHA1.hexdigest(@identifier)}.xml"
102
+ end
103
+ end
104
+
105
+ def linkname
106
+ if aggregate?
107
+ raise "Not supported for aggregates"
108
+ else
109
+ "#{Digest::SHA1.hexdigest(entity_id)}.xml"
110
+ end
111
+ end
112
+
113
+ def symlink?
114
+ File.symlink?(filename)
115
+ end
116
+
117
+ private
118
+
119
+ def calculate_type
120
+ return :aggregate if data.include?("<EntitiesDescriptor")
121
+ if data.include?("EntityDescriptor")
122
+ return :alias if symlink?
123
+ return :entity
124
+ end
125
+ :unknown
126
+ end
127
+
128
+ def xml_doc
129
+ Nokogiri::XML.parse(data).remove_namespaces!
130
+ end
131
+
132
+ def extract_entity_id
133
+ xml_doc.xpath("/EntityDescriptor/@entityID").text
134
+ end
135
+
136
+ def extract_entity_ids
137
+ xml_doc.xpath("//EntityDescriptor/@entityID").map(&:text)
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+
144
+ end
@@ -0,0 +1,182 @@
1
+ module MDQT
2
+ class Client
3
+
4
+ class MetadataResponse
5
+
6
+ require 'digest'
7
+ require 'uri'
8
+
9
+ def initialize(identifier, service, http_response, options = {})
10
+
11
+ @requested_identitier = identifier
12
+ @identifier = URI.decode_www_form_component(identifier)
13
+ @service = service
14
+ @code = http_response.status || 500
15
+ @data = http_response.body || ""
16
+ @type = nil
17
+ @content_type = http_response.headers['Content-Type']
18
+ @expires = http_response.headers['Expires']
19
+ @etag = http_response.headers['ETag']
20
+ @last_modified = http_response.headers['Last-Modified']
21
+ @ok = http_response.success?
22
+ @explanation = {}
23
+
24
+ if options[:explain]
25
+ @explanation[:url] = http_response.env.url.to_s
26
+ @explanation[:method] = http_response.env.method.to_s.upcase
27
+ @explanation[:status] = http_response.status
28
+ @explanation[:response_headers] = http_response.headers
29
+ @explanation[:request_headers] = http_response.env.request_headers
30
+ end
31
+
32
+ end
33
+
34
+ def identifier
35
+ @identifier
36
+ end
37
+
38
+
39
+ def requested_identifier
40
+ @identifier
41
+ end
42
+
43
+ def entity_id
44
+ raise "Incorrect metadata file type - aggregate" if aggregate?
45
+ @entity_id ||= extract_entity_id
46
+ end
47
+
48
+ def entity_ids
49
+ @entity_ids ||= extract_entity_ids
50
+ end
51
+
52
+
53
+ def service
54
+ @service
55
+ end
56
+
57
+ def code
58
+ @code
59
+ end
60
+
61
+ def data
62
+ @data || ""
63
+ end
64
+
65
+ def type
66
+ @type ||= calculate_type
67
+ end
68
+
69
+ def content_type
70
+ @content_type
71
+ end
72
+
73
+ def expires
74
+ @expires
75
+ end
76
+
77
+ def etag
78
+ @etag
79
+ end
80
+
81
+ def last_modified
82
+ @last_modified
83
+ end
84
+
85
+ def ok?
86
+ @ok
87
+ end
88
+
89
+ def signed?
90
+ @data.include? "Signature" # This is... not great
91
+ end
92
+
93
+ def verified_signature?(certs = [], _ = {})
94
+ return true unless ok? # CHECK ?
95
+ validator = MetadataValidator.new(certs: [certs].flatten)
96
+ validator.verified_signature?(self)
97
+ end
98
+
99
+ def valid?
100
+ validator = MetadataValidator.new
101
+ validator.valid?(self)
102
+ end
103
+
104
+ def validation_error
105
+ validator = MetadataValidator.new
106
+ validator.validation_error(self)
107
+ end
108
+
109
+ def filename
110
+ if identifier.empty?
111
+ @filename = "aggregate-#{Digest::SHA1.hexdigest(@service)}.xml"
112
+ else
113
+ @filename ||= identifier.start_with?("{sha1}") ?
114
+ "#{@identifier.gsub("{sha1}","")}.xml" :
115
+ "#{Digest::SHA1.hexdigest(@identifier)}.xml"
116
+ end
117
+ end
118
+
119
+ def message
120
+ case code
121
+ when 200
122
+ identifier.empty? ? "[200] OK! Data for aggregate has been downloaded from #{service}" :
123
+ "[200] OK! Data for '#{identifier}' has been downloaded from #{service}"
124
+ when 304
125
+ "[304] OK! Data for '#{identifier}' is already available in a local cache"
126
+ when 400
127
+ "[400] The identifier '#{identifier}' ('#{requested_identifier}') is malformed or service URL #{service} is incorrect"
128
+ when 401
129
+ "[401] Credentials are incorrect or missing for '#{identifier}'"
130
+ when 403
131
+ identifier.empty? ? "[403] The MDQ service at #{service} does not support aggregate downloads" :
132
+ "[403] You do not have access rights to '#{identifier}' at #{service}"
133
+ when 404
134
+ identifier.empty? ? "[404] The MDQ service at #{service} is not responding with aggregated metadata or the correct status" :
135
+ "[404] Entity metadata for '#{identifier}' was not found at #{service}"
136
+ when 405
137
+ "[405] The service at #{service} believes the wrong HTTP method was used. We should have used HTTP GET..."
138
+ when 406
139
+ "[406] The requested content type is not available at #{service}"
140
+ when 500
141
+ "[500] An error has occurred at #{service}"
142
+ when 505
143
+ "[505] The service at #{service} claims our request was using pre-1999 web protocols, not HTTP 1.1 or later"
144
+ else
145
+ "[#{code}] Sorry - an unknown error has occurred requesting '#{identifier}' from #{service}.\nPlease report this bug!"
146
+ end
147
+ end
148
+
149
+ def explanation
150
+ @explanation
151
+ end
152
+
153
+ private
154
+
155
+ def calculate_type
156
+
157
+ return :html if data[0,1000].include?('<!doctype html>')
158
+ return :aggregate if data[0,5000].include?("EntitiesDescriptor")
159
+ if data[0,5000].include?("EntityDescriptor")
160
+ return :alias if symlink?
161
+ return :entity
162
+ end
163
+ :unknown
164
+ end
165
+
166
+ def xml_doc
167
+ Nokogiri::XML.parse(data).remove_namespaces!
168
+ end
169
+
170
+ def extract_entity_id
171
+ xml_doc.xpath("/EntityDescriptor/@entityID").text
172
+ end
173
+
174
+ def extract_entity_ids
175
+ xml_doc.xpath("//EntityDescriptor/@entityID").map(&:text)
176
+ end
177
+
178
+ end
179
+
180
+ end
181
+
182
+ end