aaf-mdqt 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/ruby.yml +41 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +296 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +168 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/Makefile +4 -0
- data/README.md +268 -0
- data/Rakefile +5 -0
- data/aaf-mdqt.gemspec +46 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cucumber.yml +2 -0
- data/exe/mdqt +174 -0
- data/lib/mdqt/cli/base.rb +190 -0
- data/lib/mdqt/cli/cache_control.rb +25 -0
- data/lib/mdqt/cli/check.rb +78 -0
- data/lib/mdqt/cli/compliance.rb +0 -0
- data/lib/mdqt/cli/defaults.rb +70 -0
- data/lib/mdqt/cli/entities.rb +47 -0
- data/lib/mdqt/cli/exists.rb +0 -0
- data/lib/mdqt/cli/get.rb +130 -0
- data/lib/mdqt/cli/list.rb +65 -0
- data/lib/mdqt/cli/ln.rb +81 -0
- data/lib/mdqt/cli/ls.rb +54 -0
- data/lib/mdqt/cli/rename.rb +75 -0
- data/lib/mdqt/cli/reset.rb +27 -0
- data/lib/mdqt/cli/services.rb +25 -0
- data/lib/mdqt/cli/transform.rb +33 -0
- data/lib/mdqt/cli/url.rb +37 -0
- data/lib/mdqt/cli/version.rb +17 -0
- data/lib/mdqt/cli.rb +24 -0
- data/lib/mdqt/client/identifier_utils.rb +51 -0
- data/lib/mdqt/client/metadata_file.rb +144 -0
- data/lib/mdqt/client/metadata_response.rb +182 -0
- data/lib/mdqt/client/metadata_service.rb +194 -0
- data/lib/mdqt/client/metadata_validator.rb +81 -0
- data/lib/mdqt/client.rb +83 -0
- data/lib/mdqt/schema/MetadataExchange.xsd +112 -0
- data/lib/mdqt/schema/mdqt_check_schema.xsd +5 -0
- data/lib/mdqt/schema/oasis-200401-wss-wssecurity-secext-1.0.xsd +195 -0
- data/lib/mdqt/schema/oasis-200401-wss-wssecurity-utility-1.0.xsd +108 -0
- data/lib/mdqt/schema/saml-schema-assertion-2.0.xsd +283 -0
- data/lib/mdqt/schema/saml-schema-metadata-2.0.xsd +337 -0
- data/lib/mdqt/schema/ws-addr.xsd +137 -0
- data/lib/mdqt/schema/ws-authorization.xsd +145 -0
- data/lib/mdqt/schema/ws-federation.xsd +471 -0
- data/lib/mdqt/schema/ws-securitypolicy-1.2.xsd +1205 -0
- data/lib/mdqt/schema/xenc-schema.xsd +136 -0
- data/lib/mdqt/schema/xml.xsd +287 -0
- data/lib/mdqt/schema/xmldsig-core-schema.xsd +309 -0
- data/lib/mdqt/version.rb +3 -0
- data/lib/mdqt.rb +5 -0
- data/lib/tasks/cucumber.rake +8 -0
- data/lib/tasks/spec.rake +5 -0
- data/lib/tasks/tests.rake +6 -0
- data/lib/tasks/yard.rake +6 -0
- metadata +332 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
module MDQT
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
require 'mdqt/cli'
|
8
|
+
require 'pastel'
|
9
|
+
require 'pathname'
|
10
|
+
|
11
|
+
def self.run(args, options)
|
12
|
+
|
13
|
+
check_requirements(options)
|
14
|
+
introduce(options)
|
15
|
+
|
16
|
+
self.new(args, options).run
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.check_requirements(options)
|
20
|
+
|
21
|
+
unless options.service == :not_required
|
22
|
+
abort "No MDQ service URL has been specified. Please use --service, MDQT_SERVICE or MDQ_BASE_URL" unless service_url(options).to_s.start_with?("http")
|
23
|
+
end
|
24
|
+
|
25
|
+
if options.save_to
|
26
|
+
dir = options.save_to
|
27
|
+
begin
|
28
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
29
|
+
rescue
|
30
|
+
abort "Error: Directory #{dir} did not exist, and we can't create it"
|
31
|
+
end
|
32
|
+
abort "Error: '#{dir}' is not a writable directory!" if (File.directory?(dir) && !File.writable?(dir))
|
33
|
+
abort "Error: '#{dir}' is not a directory!" unless File.directory?(dir)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.introduce(options)
|
39
|
+
if options.verbose
|
40
|
+
STDERR.puts "MDQT version #{MDQT::VERSION}"
|
41
|
+
STDERR.puts "Using #{service_url(options)}" unless options.service == :not_required
|
42
|
+
STDERR.puts "Caching is #{MDQT::CLI::CacheControl.caching_on?(options) ? 'on' : 'off'}"
|
43
|
+
STDERR.print "XML validation is #{MDQT::Client.verification_available? ? 'available' : 'not available'}"
|
44
|
+
STDERR.puts " #{options.validate ? "and active" : "but inactive"} for this request" if MDQT::Client.verification_available?
|
45
|
+
STDERR.print "Signature verification is #{MDQT::Client.verification_available? ? 'available' : 'not available'}"
|
46
|
+
STDERR.puts " #{options.verify_with ? "and active" : "but inactive"} for this request" if MDQT::Client.verification_available?
|
47
|
+
STDERR.puts "Output directory for saved files is: #{File.absolute_path(options.save_to)}" if options.save_to
|
48
|
+
STDERR.puts("Warning! TLS certificate verification has been disabled!") if options.tls_risky
|
49
|
+
STDERR.puts
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(cli_args, options)
|
54
|
+
piped_input = get_stdin
|
55
|
+
@args = cli_args.concat(piped_input)
|
56
|
+
@options = options
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_stdin
|
60
|
+
return $stdin.readlines.map(&:split).flatten.map(&:strip) if pipeable?
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
|
64
|
+
def pipeable?
|
65
|
+
return false if ENV["MDQT_STDIN"].to_s.strip.downcase == "off" # Workaround Aruba testing weirdness?
|
66
|
+
!STDIN.tty? && !$stdin.closed? && $stdin.stat.pipe?
|
67
|
+
end
|
68
|
+
|
69
|
+
def args
|
70
|
+
@args
|
71
|
+
end
|
72
|
+
|
73
|
+
def options=(new_options)
|
74
|
+
@options = new_options
|
75
|
+
end
|
76
|
+
|
77
|
+
def options
|
78
|
+
@options
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.service_url(options)
|
82
|
+
|
83
|
+
return nil if options.service == :not_required
|
84
|
+
|
85
|
+
choice = options.service.to_s.strip
|
86
|
+
|
87
|
+
if choice.downcase.start_with? "http"
|
88
|
+
normalize_base_url(choice)
|
89
|
+
else
|
90
|
+
Defaults.lookup_service_alias(choice)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
def service_url(options)
|
96
|
+
self.class.service_url(options)
|
97
|
+
end
|
98
|
+
|
99
|
+
def output(response)
|
100
|
+
if response.ok?
|
101
|
+
yay response.message
|
102
|
+
hey explain(response) if options.explain
|
103
|
+
trailer = response.data[-1] == "\n" ? "" : "\n"
|
104
|
+
response.data + trailer
|
105
|
+
else
|
106
|
+
hey response.message
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def explain(response)
|
112
|
+
unless response.explanation.empty?
|
113
|
+
require 'terminal-table'
|
114
|
+
misc_rows = [['URL', response.explanation[:url]], ["Method", response.explanation[:method]], ['Status', response.explanation[:status]]]
|
115
|
+
req_header_rows = response.explanation[:request_headers].map { |k, v| ['C', k, v] }
|
116
|
+
resp_header_rows = response.explanation[:response_headers].map { |k, v| ['S', k, v] }
|
117
|
+
|
118
|
+
btw Terminal::Table.new :title => "HTTP Misc", :rows => misc_rows
|
119
|
+
btw Terminal::Table.new :title => "Client Request Headers", :headings => ['C/S', 'Header', 'Value'], :rows => req_header_rows
|
120
|
+
btw Terminal::Table.new :title => "Server Response Headers", :headings => ['C/S', 'Header', 'Value'], :rows => resp_header_rows
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def advise_on_xml_signing_support
|
126
|
+
hey "XML signature verification and XML validation are not available. Install the 'xmldsig' gem if you can." unless MDQT::Client.verification_available?
|
127
|
+
end
|
128
|
+
|
129
|
+
def extract_certificate_paths(cert_paths = options.verify_with)
|
130
|
+
cert_paths.collect do |cert_path|
|
131
|
+
begin
|
132
|
+
halt! "Cannot read certificate at '#{cert_path}'!" unless File.readable?(cert_path)
|
133
|
+
halt! "File at '#{cert_path}' does not seem to be a PEM format certificate" unless IO.binread(cert_path).include?("-----BEGIN CERTIFICATE-----")
|
134
|
+
cert_path
|
135
|
+
rescue
|
136
|
+
halt! "Unable to validate the certificate at '#{cert_path}'"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def colour_shell?
|
142
|
+
TTY::Color.color?
|
143
|
+
end
|
144
|
+
|
145
|
+
def pastel
|
146
|
+
@pastel ||= Pastel.new
|
147
|
+
end
|
148
|
+
|
149
|
+
def say(text)
|
150
|
+
STDOUT.puts(text)
|
151
|
+
end
|
152
|
+
|
153
|
+
def hey(comment)
|
154
|
+
STDERR.puts(comment)
|
155
|
+
end
|
156
|
+
|
157
|
+
def btw(comment)
|
158
|
+
STDERR.puts(comment) if options.verbose
|
159
|
+
end
|
160
|
+
|
161
|
+
def yay(comment)
|
162
|
+
btw pastel.green(comment)
|
163
|
+
end
|
164
|
+
|
165
|
+
def halt!(comment)
|
166
|
+
abort pastel.red("Error: #{comment}")
|
167
|
+
end
|
168
|
+
|
169
|
+
def run
|
170
|
+
halt! "No action has been defined for this command!"
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
## Base URLs should end with a "/", it might be easier to just add one rather than raise an error
|
176
|
+
def self.normalize_base_url(url)
|
177
|
+
if url.end_with?('/')
|
178
|
+
url
|
179
|
+
else
|
180
|
+
"#{url}/"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
end
|
190
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module MDQT
|
2
|
+
class CLI
|
3
|
+
|
4
|
+
class CacheControl
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def caching_on?(options)
|
9
|
+
return false if cache_type(options) == :none
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def cache_type(options)
|
14
|
+
return :none if options.refresh
|
15
|
+
return :memcache if options.cache && options.memcache
|
16
|
+
return :file if options.cache
|
17
|
+
:none
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module MDQT
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
require 'mdqt/cli/base'
|
6
|
+
|
7
|
+
class Check < 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
|
+
service_url(options),
|
18
|
+
verbose: options.verbose,
|
19
|
+
explain: options.explain ? true : false,
|
20
|
+
)
|
21
|
+
|
22
|
+
cert_paths = options.verify_with ? extract_certificate_paths(options.verify_with) : []
|
23
|
+
|
24
|
+
args.each do |filename|
|
25
|
+
|
26
|
+
filename = File.absolute_path(filename)
|
27
|
+
file = client.open_metadata(filename)
|
28
|
+
|
29
|
+
halt!("Cannot access file #{filename}") unless file.readable?
|
30
|
+
|
31
|
+
halt!("XML validation failed for #{filename}:\n#{file.validation_error}") unless file.valid?
|
32
|
+
btw "File #{filename} is valid SAML Metadata XML"
|
33
|
+
|
34
|
+
if options.verify_with
|
35
|
+
halt! "XML in #{filename} is not signed, cannot verify!" unless file.signed?
|
36
|
+
halt! "The signed XML for #{filename} cannot be verified using #{cert_paths.to_sentence}" unless file.verified_signature?(cert_paths)
|
37
|
+
btw "Signed XML for #{filename} has been verified using '#{cert_paths.to_sentence}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
yay "#{filename} OK"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def verify_results(results)
|
46
|
+
|
47
|
+
# if options.validate
|
48
|
+
# results.each do |result|
|
49
|
+
# next unless result.ok?
|
50
|
+
# halt! "The data for #{result.identifier} is not valid when checked against schema:\n#{result.validation_error}" unless result.valid?
|
51
|
+
# btw "Data for #{result.identifier.empty? ? 'aggregate' : result.identifier } has been validated against schema" ## FIXME - needs constistent #label maybe?
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# return results unless options.verify_with
|
56
|
+
#
|
57
|
+
# cert_paths = extract_certificate_paths(options.verify_with)
|
58
|
+
#
|
59
|
+
# results.each do |result|
|
60
|
+
# next unless result.ok?
|
61
|
+
# halt! "Data from #{options.service} is not signed, cannot verify!" unless result.signed?
|
62
|
+
# halt! "The data for #{result.identifier} cannot be verified using #{cert_paths.to_sentence}" unless result.verified_signature?(cert_paths)
|
63
|
+
# btw "Data for #{result.identifier.empty? ? 'aggregate' : result.identifier } has been verified using '#{cert_paths.to_sentence}'" ## FIXME - needs constistent #label maybe?
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# results
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
File without changes
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module MDQT
|
2
|
+
class CLI
|
3
|
+
|
4
|
+
class Defaults
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def base_url
|
9
|
+
|
10
|
+
ENV['MDQT_SERVICE'] || ENV['MDQ_BASE_URL'] || guess_service
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def force_hash?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def cli_defaults
|
19
|
+
{
|
20
|
+
hash: force_hash?,
|
21
|
+
cache: true,
|
22
|
+
refresh: false
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def guess_service
|
27
|
+
|
28
|
+
locale = ENV['LANG']
|
29
|
+
|
30
|
+
service = services.find { |s| s[:locale] == locale }
|
31
|
+
#service ||= services.first
|
32
|
+
|
33
|
+
if service
|
34
|
+
url = service[:url]
|
35
|
+
STDERR.puts "MDQT is assuming that you want to use #{url}\nPlease configure this using --service, MDQT_SERVICE or MDQ_BASE_URL\n\n"
|
36
|
+
url
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def lookup_service_alias(srv_alias)
|
44
|
+
service = services.find { |s| s[:alias].to_s.downcase.to_sym == srv_alias.to_s.downcase.to_sym }
|
45
|
+
service ? service[:url] : nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def services
|
49
|
+
[
|
50
|
+
{ alias: "ukamf",
|
51
|
+
locale: "en_GB.UTF-8",
|
52
|
+
url: "http://mdq.ukfederation.org.uk/"
|
53
|
+
},
|
54
|
+
{ alias: "incommon",
|
55
|
+
locale: "en_US.UTF-8",
|
56
|
+
url: "https://mdq.incommon.org/"
|
57
|
+
},
|
58
|
+
{ alias: "dfn",
|
59
|
+
locale: "de_utf8",
|
60
|
+
url: "https://mdq.aai.dfn.de/"
|
61
|
+
},
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module MDQT
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
require 'mdqt/cli/base'
|
6
|
+
|
7
|
+
class Entities < 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
|
+
service_url(options),
|
18
|
+
verbose: options.verbose,
|
19
|
+
explain: options.explain ? true : false,
|
20
|
+
)
|
21
|
+
|
22
|
+
args.each do |filename|
|
23
|
+
|
24
|
+
file = client.open_metadata(filename)
|
25
|
+
|
26
|
+
halt!("Cannot access file #{filename}") unless file.readable?
|
27
|
+
|
28
|
+
halt!("XML validation failed for #{filename}:\n#{file.validation_error}") unless file.valid?
|
29
|
+
|
30
|
+
file.entity_ids.each do |id|
|
31
|
+
id = options.sha1 ? [id, MDQT::Client::IdentifierUtils.transform_uri(id)].join(" ") : id
|
32
|
+
say(id)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
File without changes
|
data/lib/mdqt/cli/get.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
module MDQT
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
require 'mdqt/cli/base'
|
6
|
+
|
7
|
+
class Get < Base
|
8
|
+
|
9
|
+
def run
|
10
|
+
|
11
|
+
aggregate_confirmation_check!
|
12
|
+
|
13
|
+
advise_on_xml_signing_support
|
14
|
+
|
15
|
+
results = verify_results(get_responses)
|
16
|
+
|
17
|
+
#results = MetadataAggregator.aggregate_responses(results) if options.aggregate
|
18
|
+
|
19
|
+
output_metadata(results, options)
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_responses
|
24
|
+
|
25
|
+
client = MDQT::Client.new(
|
26
|
+
service_url(options),
|
27
|
+
verbose: options.verbose,
|
28
|
+
explain: options.explain ? true : false,
|
29
|
+
tls_risky: options.tls_risky ? true : false,
|
30
|
+
cache_type: MDQT::CLI::CacheControl.cache_type(options),
|
31
|
+
)
|
32
|
+
|
33
|
+
args.empty? ? [client.get_metadata("")] : args.collect { |entity_id| client.get_metadata(entity_id) }
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify_results(results)
|
38
|
+
|
39
|
+
if options.validate
|
40
|
+
results.each do |result|
|
41
|
+
next unless result.ok?
|
42
|
+
halt! "The data for #{result.identifier} is not valid when checked against schema:\n#{result.validation_error}" unless result.valid?
|
43
|
+
btw "Data for #{result.identifier.empty? ? 'aggregate' : result.identifier } has been validated against schema" ## FIXME - needs constistent #label maybe?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
return results unless options.verify_with
|
48
|
+
|
49
|
+
cert_paths = extract_certificate_paths(options.verify_with)
|
50
|
+
|
51
|
+
results.each do |result|
|
52
|
+
next unless result.ok?
|
53
|
+
halt! "Data from #{options.service} is not signed, cannot verify!" unless result.signed?
|
54
|
+
halt! "The data for #{result.identifier} cannot be verified using #{cert_paths.to_sentence}" unless result.verified_signature?(cert_paths)
|
55
|
+
btw "Data for #{result.identifier.empty? ? 'aggregate' : result.identifier } has been verified using '#{cert_paths.to_sentence}'" ## FIXME - needs constistent #label maybe?
|
56
|
+
end
|
57
|
+
|
58
|
+
results
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def output_metadata(results, options)
|
63
|
+
case action(results, options)
|
64
|
+
when :save_files
|
65
|
+
output_files(results, options)
|
66
|
+
when :print_to_stdout
|
67
|
+
output_to_stdout(results, options)
|
68
|
+
else
|
69
|
+
halt! "Can't determine output type"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def action(results, options)
|
74
|
+
case
|
75
|
+
when options.save_to
|
76
|
+
:save_files
|
77
|
+
else
|
78
|
+
:print_to_stdout
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def output_to_stdout(results, options)
|
83
|
+
results.each { |r| puts output(r) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def output_files(results, options)
|
87
|
+
pwd = Pathname.getwd
|
88
|
+
prepare_output_directory(options.save_to)
|
89
|
+
results.each do |result|
|
90
|
+
main_file = output_file_path(result.filename)
|
91
|
+
open(main_file, 'w') { |f|
|
92
|
+
f.puts result.data
|
93
|
+
}
|
94
|
+
|
95
|
+
if options.list
|
96
|
+
puts Pathname.new(main_file).relative_path_from(pwd)
|
97
|
+
end
|
98
|
+
|
99
|
+
yay "Created #{main_file}"
|
100
|
+
|
101
|
+
# if options.link_id
|
102
|
+
# ["{sha1}#{result.filename.gsub(".xml", "")}"].each do |altname|
|
103
|
+
# full_alias = output_file_path(altname)
|
104
|
+
# FileUtils.ln_sf(main_file, full_alias)
|
105
|
+
# yay "Linked alias #{altname} -> #{main_file}"
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def output_file_path(filename)
|
115
|
+
File.absolute_path(File.join([options.save_to, filename]))
|
116
|
+
end
|
117
|
+
|
118
|
+
def prepare_output_directory(directory)
|
119
|
+
FileUtils.mkdir_p(directory)
|
120
|
+
end
|
121
|
+
|
122
|
+
def aggregate_confirmation_check!
|
123
|
+
halt!("Please specify --all if you wish to request all entities from #{options.service}") if args.empty? && !options.all
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module MDQT
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
require 'mdqt/cli/base'
|
6
|
+
|
7
|
+
class List < Base
|
8
|
+
|
9
|
+
def run
|
10
|
+
|
11
|
+
options.validate = true
|
12
|
+
|
13
|
+
advise_on_xml_signing_support
|
14
|
+
|
15
|
+
halt!("Cannot check a metadata file without XML support: please install additional gems") unless MDQT::Client.verification_available?
|
16
|
+
|
17
|
+
response = get_response
|
18
|
+
result = verify_result(response)
|
19
|
+
|
20
|
+
puts result.entity_ids
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_response
|
25
|
+
|
26
|
+
client = MDQT::Client.new(
|
27
|
+
service_url(options),
|
28
|
+
verbose: options.verbose,
|
29
|
+
explain: options.explain ? true : false,
|
30
|
+
tls_risky: options.tls_risky ? true : false,
|
31
|
+
cache_type: MDQT::CLI::CacheControl.cache_type(options),
|
32
|
+
)
|
33
|
+
|
34
|
+
client.get_metadata("")
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def verify_result(result)
|
39
|
+
|
40
|
+
if options.validate
|
41
|
+
halt! "The data for #{result.identifier} is not valid when checked against schema:\n#{result.validation_error}" unless result.valid?
|
42
|
+
btw "Data for #{result.identifier.empty? ? 'aggregate' : result.identifier } has been validated against schema" ## FIXME - needs constistent #label maybe?
|
43
|
+
end
|
44
|
+
|
45
|
+
return result unless options.verify_with
|
46
|
+
|
47
|
+
cert_paths = extract_certificate_paths(options.verify_with)
|
48
|
+
|
49
|
+
halt! "Data from #{options.service} is not signed, cannot verify!" unless result.signed?
|
50
|
+
halt! "The data for #{result.identifier} cannot be verified using #{cert_paths.to_sentence}" unless result.verified_signature?(cert_paths)
|
51
|
+
btw "Data for #{result.identifier.empty? ? 'aggregate' : result.identifier } has been verified using '#{cert_paths.to_sentence}'" ## FIXME - needs constistent #label maybe?
|
52
|
+
|
53
|
+
result
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
data/lib/mdqt/cli/ln.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module MDQT
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
require 'mdqt/cli/base'
|
6
|
+
|
7
|
+
class Ln < 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 link to!") 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 create entityID hashed link!") 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
|
+
linkname = file.linkname
|
37
|
+
|
38
|
+
if filename == linkname
|
39
|
+
if options.force
|
40
|
+
hey("Warning: Cannot link file to itself, skipping! #{filename}")
|
41
|
+
next
|
42
|
+
else
|
43
|
+
halt!("Cannot link file to itself! #{filename}")
|
44
|
+
next
|
45
|
+
end
|
46
|
+
btw("Cannot link file to itself! #{filename}")
|
47
|
+
end
|
48
|
+
|
49
|
+
if file.turd?
|
50
|
+
hey "Warning: will not process backup/turd files"
|
51
|
+
next
|
52
|
+
end
|
53
|
+
|
54
|
+
message = ""
|
55
|
+
|
56
|
+
if File.exist?(linkname)
|
57
|
+
if options.force
|
58
|
+
File.delete(linkname)
|
59
|
+
else
|
60
|
+
old_target = File.readlink(linkname)
|
61
|
+
message = old_target == filename ? "File exists" : "Conflicts with #{filename}"
|
62
|
+
halt!("#{linkname} -> #{old_target} [#{file.entity_id}] #{message}. Use --force to override")
|
63
|
+
next
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
File.symlink(filename, linkname)
|
68
|
+
hey("#{linkname} -> #{filename} [#{file.entity_id}] #{message}") if options.verbose
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|