shibkit-meta_meta 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +21 -0
  4. data/Gemfile.lock +52 -0
  5. data/Icon.png +0 -0
  6. data/LICENSE.txt +177 -0
  7. data/README.md +789 -0
  8. data/Rakefile +38 -0
  9. data/VERSION +1 -0
  10. data/examples/biggest_entity_id.rb +4 -0
  11. data/lib/shibkit/meta_meta.rb +600 -0
  12. data/lib/shibkit/meta_meta/attribute.rb +73 -0
  13. data/lib/shibkit/meta_meta/config.rb +463 -0
  14. data/lib/shibkit/meta_meta/contact.rb +85 -0
  15. data/lib/shibkit/meta_meta/data/default_metadata/example_federation_metadata.xml +168 -0
  16. data/lib/shibkit/meta_meta/data/default_metadata/local_metadata.xml +66 -0
  17. data/lib/shibkit/meta_meta/data/default_metadata/uncommon_federation_metadata.xml +115 -0
  18. data/lib/shibkit/meta_meta/data/default_metadata_cache.yml +166 -0
  19. data/lib/shibkit/meta_meta/data/dev_sources.yml +86 -0
  20. data/lib/shibkit/meta_meta/data/real_sources.yml +163 -0
  21. data/lib/shibkit/meta_meta/entity.rb +219 -0
  22. data/lib/shibkit/meta_meta/federation.rb +161 -0
  23. data/lib/shibkit/meta_meta/idp.rb +81 -0
  24. data/lib/shibkit/meta_meta/logo.rb +216 -0
  25. data/lib/shibkit/meta_meta/metadata_item.rb +244 -0
  26. data/lib/shibkit/meta_meta/mixin/cached_downloads.rb +127 -0
  27. data/lib/shibkit/meta_meta/mixin/xpath_chores.rb +111 -0
  28. data/lib/shibkit/meta_meta/organisation.rb +73 -0
  29. data/lib/shibkit/meta_meta/provider.rb +195 -0
  30. data/lib/shibkit/meta_meta/provisioning/base.rb +33 -0
  31. data/lib/shibkit/meta_meta/requested_attribute.rb +29 -0
  32. data/lib/shibkit/meta_meta/service.rb +94 -0
  33. data/lib/shibkit/meta_meta/source.rb +558 -0
  34. data/lib/shibkit/meta_meta/sp.rb +79 -0
  35. data/shibkit-meta_meta.gemspec +154 -0
  36. data/spec/meta_meta/attribute/token +0 -0
  37. data/spec/meta_meta/config/autoloading_and_refreshing_spec.rb +72 -0
  38. data/spec/meta_meta/config/code_nspec.rb +13 -0
  39. data/spec/meta_meta/config/configuration_spec.rb +30 -0
  40. data/spec/meta_meta/config/creation_spec.rb +43 -0
  41. data/spec/meta_meta/config/downloading_and_caching_settings_spec.rb +216 -0
  42. data/spec/meta_meta/config/env_platform_settings.rb +129 -0
  43. data/spec/meta_meta/config/filtering_settings_spec.rb +123 -0
  44. data/spec/meta_meta/config/init.rb +8 -0
  45. data/spec/meta_meta/config/logger_settings_spec.rb +91 -0
  46. data/spec/meta_meta/config/smartcache_settings_spec.rb +110 -0
  47. data/spec/meta_meta/config/source_file_settings_spec.rb +99 -0
  48. data/spec/meta_meta/config/tagging_settings_spec.rb +81 -0
  49. data/spec/meta_meta/config/working_directory_settings_spec.rb +106 -0
  50. data/spec/meta_meta/config/xml_processing_settings_spec.rb +75 -0
  51. data/spec/meta_meta/contact/contact_oldspec.rb +0 -0
  52. data/spec/meta_meta/entity/entity_oldspec.rb +53 -0
  53. data/spec/meta_meta/federation/federation_oldspec.rb +0 -0
  54. data/spec/meta_meta/idp/token +0 -0
  55. data/spec/meta_meta/logo/token +0 -0
  56. data/spec/meta_meta/meta_meta/cache_example.yaml +141284 -0
  57. data/spec/meta_meta/meta_meta/meta_meta_spec.rb +269 -0
  58. data/spec/meta_meta/meta_meta/saved_sources.yaml +46 -0
  59. data/spec/meta_meta/metadata_item/token +0 -0
  60. data/spec/meta_meta/organisation/organisation_oldspec.rb +0 -0
  61. data/spec/meta_meta/provider/token +0 -0
  62. data/spec/meta_meta/requested_attribute/token +0 -0
  63. data/spec/meta_meta/service/token +0 -0
  64. data/spec/meta_meta/source/application_extras_spec.rb +234 -0
  65. data/spec/meta_meta/source/conversion_spec.rb +75 -0
  66. data/spec/meta_meta/source/creation_spec.rb +0 -0
  67. data/spec/meta_meta/source/downloads_and_caching_spec.rb +0 -0
  68. data/spec/meta_meta/source/federation_information_spec.rb +11 -0
  69. data/spec/meta_meta/source/fixtures.rb +24 -0
  70. data/spec/meta_meta/source/init.rb +1 -0
  71. data/spec/meta_meta/source/loading_and_saving_spec.rb +0 -0
  72. data/spec/meta_meta/source/metadata_details_spec.rb +0 -0
  73. data/spec/meta_meta/source/metadata_integrity_spec.rb +0 -0
  74. data/spec/meta_meta/source/selection_spec.rb +0 -0
  75. data/spec/meta_meta/source/source_oldspec.rb +353 -0
  76. data/spec/meta_meta/source/xml_parsing_spec.rb +0 -0
  77. data/spec/meta_meta/sp/token +0 -0
  78. data/spec/meta_meta/template +2 -0
  79. data/spec/moi/config_spec.rb +0 -0
  80. data/spec/spec.opts +1 -0
  81. data/spec/spec_helper.rb +25 -0
  82. data/spec/support/supply_xml.rb +0 -0
  83. metadata +320 -0
@@ -0,0 +1,73 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+ module Shibkit
19
+ class MetaMeta
20
+
21
+ ## Class to represent the metadata of the organisation owning a Shibboleth entity
22
+ class Organisation < MetadataItem
23
+
24
+ require 'shibkit/meta_meta/metadata_item'
25
+
26
+ ## Element and attribute used to select XML for new objects
27
+ ROOT_ELEMENT = 'Organization'
28
+ TARGET_ATTR = nil
29
+ REQUIRED_QUACKS = [:name]
30
+
31
+ ## The name identifier for the organisation
32
+ attr_accessor :name
33
+
34
+ ## The human-readable display name for the organisation
35
+ attr_accessor :display_name
36
+
37
+ ## The homepage URL for the organisation
38
+ attr_accessor :url
39
+
40
+ ## Try to make a crude unique id for the organisation
41
+ def druid
42
+
43
+ ## Derived, *relatively* unique ID.
44
+ return display_name.strip.downcase.delete " .,-_'"
45
+
46
+ end
47
+
48
+ def to_s
49
+
50
+ return display_name
51
+
52
+ end
53
+
54
+ private
55
+
56
+ def parse_xml
57
+
58
+ @name = @noko.xpath('xmlns:OrganizationName[1]')[0].content.strip
59
+
60
+
61
+ @display_name = @noko.xpath('xmlns:OrganizationDisplayName[1]')[0].content.strip
62
+
63
+
64
+ @url = @noko.xpath('xmlns:OrganizationURL[1]')[0].content.strip
65
+
66
+
67
+ log.debug " Derived organisation #{url} from XML"
68
+
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,195 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+ module Shibkit
19
+ class MetaMeta
20
+
21
+ require 'shibkit/meta_meta/metadata_item'
22
+
23
+ ## Class to represent the metadata of a Shibboleth IDP or SP
24
+ class Provider < MetadataItem
25
+
26
+ require 'shibkit/meta_meta/logo'
27
+
28
+ ## Element and attribute used to select XML for new objects
29
+ ROOT_ELEMENT = 'EntityDescriptor'
30
+ TARGET_ATTR = 'entityID'
31
+ REQUIRED_QUACKS = [:entity_uri]
32
+
33
+ MDUI_ROOT = 'NotSpecified'
34
+
35
+ ## The URI of the entity
36
+ attr_accessor :entity_uri
37
+ alias :uri :entity_uri
38
+
39
+ attr_accessor :display_names
40
+
41
+ attr_accessor :descriptions
42
+
43
+ attr_accessor :keyword_sets
44
+
45
+ attr_accessor :info_urls
46
+
47
+ attr_accessor :privacy_urls
48
+
49
+ attr_accessor :ip_blocks
50
+
51
+ attr_accessor :domains
52
+
53
+ attr_accessor :geolocation_urls
54
+
55
+ attr_reader :valid
56
+
57
+ attr_accessor :organisation
58
+
59
+ alias :entity_id :entity_uri
60
+ alias :valid? :valid
61
+
62
+ def to_s
63
+
64
+ return uri
65
+
66
+ end
67
+
68
+ def display_name(lang=:en)
69
+
70
+ return display_names[lang] unless display_names[lang].to_s.empty?
71
+
72
+ if self.kind_of?(Shibkit::MetaMeta::SP) and default_service
73
+ return self.default_service.name[lang] unless default_service.name[lang].to_s.empty?
74
+ end
75
+
76
+ if organisation
77
+ return organisation.display_name unless organisation.display_name.to_s.empty?
78
+ return [organisation.name, "service"].join(' ') unless organisation.name.to_s.empty?
79
+ end
80
+
81
+ return entity_id
82
+
83
+ end
84
+
85
+ def description(lang=:en)
86
+
87
+ return descriptions[lang] unless descriptions[lang].to_s.empty?
88
+
89
+ if self.kind_of?(Shibkit::MetaMeta::SP) and default_service
90
+ return default_service.description(lang) unless default_service.description(lang).to_s.empty?
91
+ end
92
+
93
+ if organisation
94
+ return organisation.display_name unless organisation.display_name.to_s.empty?
95
+ return organisation.name unless organisation.name.to_s.empty?
96
+ end
97
+
98
+ return ""
99
+
100
+ end
101
+
102
+ def keywords(lang=:en)
103
+
104
+ return keyword_sets[lang] || []
105
+
106
+ end
107
+
108
+ def info_url(lang=:en)
109
+
110
+ return info_urls[lang] || nil
111
+
112
+ end
113
+
114
+ def privacy_url(lang=:en)
115
+
116
+ return privacy_urls[lang] || nil
117
+
118
+ end
119
+
120
+ def logos(lang=:en)
121
+
122
+ return logos[lang] || []
123
+
124
+ end
125
+
126
+ def purge_xml(cascade=true)
127
+
128
+ super
129
+
130
+ return unless cascade
131
+
132
+ @logos.values.each do |logo_set|
133
+
134
+ logo_set.each { |logo| logo.purge_xml(cascade)}
135
+
136
+ end
137
+
138
+ end
139
+
140
+ def textify_xml(cascade=true)
141
+
142
+ super
143
+
144
+ return unless cascade
145
+
146
+ @logos.values.each do |logo_set|
147
+
148
+ logo_set.each { |logo| logo.textify_xml(cascade)}
149
+
150
+ end
151
+
152
+ end
153
+
154
+ private
155
+
156
+ def parse_xml
157
+
158
+ self.entity_uri = @noko['entityID']
159
+
160
+ mdui_root = self.class::MDUI_ROOT
161
+
162
+ ## Display names
163
+ @display_names = extract_lang_map_of_strings("xmlns:#{mdui_root}/xmlns:Extensions/mdui:UIInfo/mdui:DisplayName")
164
+
165
+ ## Descriptions
166
+ @descriptions = extract_lang_map_of_strings("xmlns:#{mdui_root}/xmlns:Extensions/mdui:UIInfo/mdui:Description")
167
+
168
+ ## Keywords
169
+ @keyword_sets = extract_lang_map_of_string_lists("xmlns:#{mdui_root}/xmlns:Extensions/mdui:UIInfo/mdui:Keywords")
170
+
171
+ ## Information URLs
172
+ @info_urls = extract_lang_map_of_strings("xmlns:#{mdui_root}/xmlns:Extensions/mdui:UIInfo/mdui:InformationURL")
173
+
174
+ ## Privacy Statement URLs
175
+ @privacy_urls = extract_lang_map_of_strings("xmlns:#{mdui_root}/xmlns:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL")
176
+
177
+ ## Logos
178
+ @logos = extract_lang_map_of_objects("xmlns:#{mdui_root}/xmlns:Extensions/mdui:UIInfo/mdui:Logo",
179
+ Shibkit::MetaMeta::Logo)
180
+
181
+ ## IP Address Ranges
182
+ @ip_blocks = extract_simple_list("xmlns:#{mdui_root}/xmlns:Extensions/mdui:DiscoHints/mdui:IPHint")
183
+
184
+ ## DNS Domain Names
185
+ @domains = extract_simple_list("xmlns:#{mdui_root}/xmlns:Extensions/mdui:DiscoHints/mdui:DomainHint")
186
+
187
+ ## Geolocations
188
+ @geolocations = extract_simple_list("xmlns:#{mdui_root}/xmlns:Extensions/mdui:DiscoHints/mdui:GeolocationHint")
189
+
190
+ end
191
+
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,33 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+
19
+ module Shibkit
20
+ class MetaMeta
21
+ module Provisioning
22
+
23
+ ## Abstract base class for provisioning drivers
24
+ class Base
25
+
26
+ initialize
27
+
28
+
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+ module Shibkit
19
+ class MetaMeta
20
+
21
+ ## Class to represent the metadata of the organisation owning a Shibboleth entity
22
+ class RequestedAttribute < Attribute
23
+
24
+ ## Element and attribute used to select XML for new objects
25
+ ROOT_ELEMENT = 'RequestedAttribute'
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+ module Shibkit
19
+ class MetaMeta
20
+
21
+ ##
22
+ class Service < MetadataItem
23
+
24
+ require 'shibkit/meta_meta/metadata_item'
25
+
26
+ ## Element and attribute used to select XML for new objects
27
+ ROOT_ELEMENT = 'AttributeConsumingService'
28
+ TARGET_ATTR = 'index'
29
+ REQUIRED_QUACKS = [:index]
30
+
31
+ ##
32
+ attr_accessor :names
33
+
34
+ ##
35
+ attr_accessor :descriptions
36
+
37
+ ##
38
+ attr_accessor :index
39
+
40
+ ##
41
+ attr_accessor :attributes
42
+
43
+ attr_accessor :default
44
+
45
+ alias :default? :default
46
+
47
+ def name(lang=:en)
48
+
49
+ return names[lang]
50
+
51
+ end
52
+
53
+ def description(lang=:en)
54
+
55
+ return descriptions[lang]
56
+
57
+ end
58
+
59
+ def to_s
60
+
61
+ return name(:en)
62
+
63
+ end
64
+
65
+ private
66
+
67
+ def parse_xml
68
+
69
+ @index = @noko['index'].to_i || 0
70
+
71
+ @default = @noko['isDefault'] || 'false'
72
+
73
+ ## Display names
74
+ @names = extract_lang_map_of_strings("xmlns:ServiceName")
75
+
76
+ ## Descriptions
77
+ @descriptions = extract_lang_map_of_strings("xmlns:ServiceDescription")
78
+
79
+ @attributes ||= Array.new
80
+ @noko.xpath('xmlns:RequestedAttribute').each do |ax|
81
+
82
+ attribute = Shibkit::MetaMeta::RequestedAttribute.new(ax).filter
83
+
84
+ @attributes << attribute if attribute
85
+
86
+ end
87
+
88
+ log.debug " Derived service #{name} from XML"
89
+
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,558 @@
1
+ ## @author Pete Birkinshaw (<pete@digitalidentitylabs.com>)
2
+ ## Copyright: Copyright (c) 2011 Digital Identity Ltd.
3
+ ## License: Apache License, Version 2.0
4
+
5
+ ## Licensed under the Apache License, Version 2.0 (the "License");
6
+ ## you may not use this file except in compliance with the License.
7
+ ## You may obtain a copy of the License at
8
+ ##
9
+ ## http://www.apache.org/licenses/LICENSE-2.0
10
+ ##
11
+ ## Unless required by applicable law or agreed to in writing, software
12
+ ## distributed under the License is distributed on an "AS IS" BASIS,
13
+ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ ## See the License for the specific language governing permissions and
15
+ ## limitations under the License.
16
+ ##
17
+
18
+ require 'yaml'
19
+ require 'rest_client'
20
+ require 'restclient/components'
21
+ require 'rack/cache'
22
+ require 'rack/commonlogger'
23
+ require 'rbconfig'
24
+ require 'tempfile'
25
+ require 'addressable/uri'
26
+ require 'fileutils'
27
+
28
+ module Shibkit
29
+ class MetaMeta
30
+
31
+ ##
32
+ ##
33
+ class Source
34
+
35
+ require 'shibkit/meta_meta/mixin/cached_downloads'
36
+ require 'shibkit/meta_meta/federation'
37
+
38
+ include Shibkit::MetaMeta::Mixin::CachedDownloads
39
+
40
+ ## @note This class currently lacks the ability to properly validate
41
+ ## metadata.
42
+
43
+ ## Location of default real sources list (contains real-world federation details) # REMOVE
44
+ REAL_SOURCES_FILE = "#{::File.dirname(__FILE__)}/data/real_sources.yml"
45
+
46
+ ## Location of default mock sources list (contains small fictional federations) # REMOVE
47
+ DEV_SOURCES_FILE = "#{::File.dirname(__FILE__)}/data/dev_sources.yml"
48
+
49
+ PERCENTAGE_PATTERN = /(\d+)\s*%/
50
+
51
+ ## Additional namespaces that Nokogiri needs to know about # TODO Move to Config?
52
+ NAMESPACES = {
53
+ 'ukfedlabel' => 'http://ukfederation.org.uk/2006/11/label',
54
+ 'elab' => 'http://eduserv.org.uk/labels',
55
+ 'wayf' => 'http://sdss.ac.uk/2006/06/WAYF',
56
+ 'mdui' => 'urn:oasis:names:tc:SAML:metadata:ui',
57
+ 'saml' => 'urn:oasis:names:tc:SAML:2.0:assertion',
58
+ 'shibmd' => 'urn:mace:shibboleth:metadata:1.0'
59
+ }
60
+
61
+ ## @return [String] the URI identifier for the federation or collection
62
+ attr_accessor :name_uri
63
+ alias :uri :name_uri
64
+
65
+ ## @return [String] the full name of the federation or collection
66
+ attr_accessor :name
67
+
68
+ ## @return [String] the common, friendler name of the federation or collection
69
+ attr_accessor :display_name
70
+
71
+ ## @return [String] :federation for proper federations, :collection for
72
+ ## simple collections of entities.
73
+ attr_accessor :type
74
+
75
+ ## @return [Fixednum] the recommended refresh period for the federation, in seconds
76
+ attr_accessor :refresh_delay
77
+
78
+ ## @return [Array] country codes for areas served by the federation
79
+ attr_accessor :countries
80
+
81
+ ## @return [String] URL or filesystem path of the metadata file to be used
82
+ attr_accessor :metadata_source
83
+ alias :url :metadata_source
84
+
85
+ ## @return [String] URL or filesystem path of the metadata certificate to be used
86
+ attr_accessor :certificate_source
87
+
88
+ ## @return [String, nil] Fingerprint of the federation certificate
89
+ attr_accessor :fingerprint
90
+
91
+ ## @return [String, nil] URL of the federation's Refeds wiki entry
92
+ attr_accessor :refeds_url
93
+ alias :refeds_info :refeds_url
94
+
95
+ ## @return [String] URL of the federation or collection's home page
96
+ attr_accessor :homepage
97
+ alias :homepage_url :homepage
98
+
99
+ ## @return [Array] Array of languages supported by the federation or collection
100
+ attr_accessor :languages
101
+
102
+ ## @return [String] Main contact email address for the federation
103
+ attr_accessor :support_email
104
+
105
+ ## @return [String] Brief description of the federation or collection
106
+ attr_accessor :description
107
+
108
+ ## @return [String] Time the metadata for this federation was last fetched
109
+ ## @note This is not persistent between uses of this class
110
+ attr_reader :fetched_at
111
+
112
+ ## @return [String] Message returned during processing
113
+ ## @deprecated Not actually used at present, not sure if this is needed...
114
+ attr_reader :messages
115
+
116
+ ## @return [String] Status of the source: indicates success of last operation
117
+ attr_reader :status
118
+
119
+ attr_accessor :active
120
+ alias :active? :active
121
+
122
+ attr_reader :created_at
123
+
124
+ private
125
+
126
+ attr_reader :metadata_tmpfile
127
+ attr_reader :certificate_tmpfile
128
+
129
+ public
130
+
131
+ ## New Source object with default values
132
+ ## @return [Source]
133
+ def initialize(&block)
134
+
135
+ @name_uri = ""
136
+ @created_at = Time.new
137
+ @name = "Unnown"
138
+ @refresh_delay = 86400
139
+ @display_name = "Unknown"
140
+ @type = "federation"
141
+ @countries = []
142
+ @metadata_source = nil
143
+ @certificate_source = nil
144
+ @fingerprint = nil
145
+ @refeds_info = nil
146
+ @homepage = nil
147
+ @languages = []
148
+ @support_email = nil
149
+ @description = ""
150
+ @certificate_tmpfile = nil
151
+ @metadata_tmpfile = nil
152
+ @active = true
153
+ @trustiness = 1
154
+ @groups = []
155
+ @tags = []
156
+
157
+ self.instance_eval(&block) if block
158
+
159
+ end
160
+
161
+ def to_s
162
+
163
+ return metadata_source || nil
164
+
165
+ end
166
+
167
+ ## Create a new source from a hash
168
+ def self.from_hash(data, uri=nil)
169
+
170
+ raise "#{data.class} is not a hash" if not data.instance_of? Hash
171
+
172
+ data = data.inject({}){|m,(k,v)| m[k.to_sym] = v; m}
173
+
174
+ new_source = self.new do |source|
175
+
176
+ source.name_uri = data[:uri] || uri
177
+ source.name = data[:name] || uri
178
+ source.refresh_delay = data[:refresh].to_i || 86400
179
+ source.display_name = data[:display_name] || data['name'] || uri
180
+ source.type = data[:type].to_sym || :collection
181
+
182
+ source.metadata_source = data[:metadata]
183
+ source.certificate_source = data[:certificate]
184
+ source.fingerprint = data[:fingerprint]
185
+ source.refeds_url = data[:refeds_info]
186
+ source.homepage = data[:homepage]
187
+
188
+ source.support_email = data[:support_email] || nil
189
+ source.description = data[:description] || ""
190
+ source.trustiness = data[:trustiness].to_f || 1
191
+
192
+ source.languages = data[:languages].inject([]){|m,v| m << v.to_s.downcase.to_sym } || [:en]
193
+ source.countries = data[:countries].inject([]){|m,v| m << v.to_s.downcase.to_sym } || []
194
+ source.groups = data[:groups] || []
195
+ source.tags = data[:tags] || []
196
+
197
+ end
198
+
199
+ return new_source
200
+
201
+ end
202
+
203
+ ## Create a new hash from a source object
204
+ def to_hash
205
+
206
+ data = Hash.new
207
+
208
+ data['uri'] = name_uri.strip
209
+ data['name'] = name
210
+ data['refresh'] = refresh_delay.to_i
211
+ data['display_name'] = display_name
212
+ data['type'] = type.to_s
213
+ data['countries'] = countries
214
+ data['metadata'] = metadata_source
215
+ data['certificate'] = certificate_source
216
+ data['fingerprint'] = fingerprint
217
+ data['refeds_info'] = refeds_url
218
+ data['homepage'] = homepage
219
+ data['languages'] = languages
220
+ data['support_email'] = support_email
221
+ data['description'] = description.strip
222
+ data['trustiness'] = trustiness
223
+ data['groups'] = groups
224
+ data['tags'] = tags
225
+
226
+ return data
227
+
228
+ end
229
+
230
+ ## Build a parsed Federation object containing Entitiess
231
+ ## @return [Shibkit::MetaMeta::Federation]
232
+ def to_federation
233
+
234
+ fx = self.parse_xml
235
+
236
+ federation = ::Shibkit::MetaMeta::Federation.new(fx)
237
+
238
+ ## Pass additional information from source into federation object
239
+ federation.name = name || uri
240
+ federation.display_name = display_name || name
241
+ federation.type = type
242
+ federation.refeds_url = refeds_info
243
+ federation.countries = countries
244
+ federation.languages = languages
245
+ federation.support_email = support_email
246
+ federation.homepage_url = homepage
247
+ federation.description = description
248
+ federation.groups = groups
249
+ federation.tags = tags
250
+ federation.trustiness = trustiness
251
+
252
+ #federation.from_xml(fx)
253
+
254
+ return federation
255
+
256
+ end
257
+
258
+ def groups=(group_names)
259
+
260
+ @groups ||= Array.new
261
+ @groups = [group_names].flatten.inject([]) {|m,v| m << v.to_s.downcase.to_sym }
262
+
263
+ end
264
+
265
+ def groups
266
+
267
+ return @groups
268
+
269
+ end
270
+
271
+ def tags=(tag_names)
272
+
273
+ @tags ||= Array.new
274
+ @tags = [tag_names].flatten.inject([]) {|m,v| m << v.to_s.downcase.to_sym }
275
+
276
+ end
277
+
278
+ def tags
279
+
280
+ return @tags
281
+
282
+ end
283
+
284
+ def trustiness=(level)
285
+
286
+ ## Convert strings
287
+ if level.kind_of? String
288
+
289
+ ## Percentages as strings become decimal fractions, otherwise directly converted.
290
+ level = level.match(PERCENTAGE_PATTERN) ? level.to_f / 100 : level.to_f
291
+
292
+ end
293
+
294
+ case
295
+ when level > 1
296
+ log.warn "Setting trustiness greater than 1 is ambiguous, so storing as 1. Use decimal fraction or percentage."
297
+ @trustiness = 1.0
298
+ when level < 0
299
+ log.warn "Setting trustiness less than 1 is ambiguous, so storing as 0. Use decimal fraction or percentage."
300
+ @trustiness = 0.0
301
+ else
302
+ @trustiness = level.to_f
303
+ end
304
+
305
+ return @trustiness
306
+
307
+ end
308
+
309
+ def trustiness
310
+
311
+ return @trustiness || 1.0 # TODO should be able to configure default trustiness
312
+
313
+ end
314
+
315
+ ## Redownload and revalidate remote files for the source
316
+ ## @return [TrueClass, FalseClass]
317
+ def refresh
318
+
319
+ unless selected?
320
+
321
+ log.info "Content for #{ uri} - skipping refresh as not selected."
322
+ return false
323
+
324
+ end
325
+
326
+ log.info "Content for #{ uri} is being refreshed..."
327
+
328
+ fetch_metadata
329
+ fetch_certificate
330
+
331
+ raise "Validation error" unless valid?
332
+
333
+ return true
334
+
335
+ end
336
+
337
+ ## Fetch remote file and store locally without validation
338
+ ## @return [File] Open filehandle for the local copy of metadata file
339
+ def fetch_metadata
340
+
341
+ @metadata_tmpfile = case metadata_source
342
+ when /^http/
343
+ fetch_remote(metadata_source)
344
+ else
345
+ fetch_local(metadata_source)
346
+ end
347
+
348
+ @fetched_at = Time.new
349
+
350
+ return @metadata_tmpfile
351
+
352
+ end
353
+
354
+ ## Fetch remote file and store locally
355
+ ## @return [File] open filehandle for the local copy of certificate file
356
+ def fetch_certificate
357
+
358
+ @certificate_tmpfile = case certificate_source
359
+ when /^http/
360
+ fetch_remote(certificate_source)
361
+ else
362
+ fetch_local(certificate_source)
363
+ end
364
+
365
+ return @certificate_tmpfile
366
+
367
+ end
368
+
369
+ ## Validates metadata and certificate or raises an exception
370
+ ## @return [TrueClass, FalseClass]
371
+ def validate
372
+
373
+ ## Check that XML is valid
374
+ # ...
375
+
376
+ ## Check that certificate is OK
377
+ # ...
378
+
379
+ ## Check that metadata has been signed OK, prob. Using XMLSecTool?
380
+ # ...
381
+
382
+ return true
383
+
384
+ end
385
+
386
+ ## Checks validity of metadata and certificate without raising exceptions
387
+ ## @return [TrueClass, FalseClass]
388
+ def valid?
389
+
390
+ begin
391
+ return true if validate
392
+ rescue
393
+ return false
394
+ end
395
+
396
+ end
397
+
398
+ ## Has this federation/source been selected by the user?
399
+ def selected?
400
+
401
+ ## If nothing has been specified then everything has been selected.
402
+ return true if ::Shibkit::MetaMeta.config.selected_federation_uris.empty?
403
+
404
+ ## If this source's uri is present in the list, then yup.
405
+ return true if ::Shibkit::MetaMeta.config.selected_federation_uris.include?(uri)
406
+
407
+ return false
408
+
409
+ end
410
+
411
+ ## The content of the certificate associated with the metadata
412
+ ## @return [String, nil]
413
+ def certificate_pem
414
+
415
+ ## Deal with caching locally, downloading, etc
416
+ refresh if ::Shibkit::MetaMeta.config.auto_refresh? and @certificate_tmpfile == nil
417
+
418
+ return IO.read(certificate_tmpfile.path)
419
+
420
+ end
421
+
422
+ ## Return raw source string from the file
423
+ ## @return [String] Metadata XML as text
424
+ def content
425
+
426
+ ## Deal with caching locally, downloading, etc
427
+ refresh if ::Shibkit::MetaMeta.config.auto_refresh? and @metadata_tmpfile == nil
428
+
429
+ raise "No content is available, source has not been downloaded" unless
430
+ metadata_tmpfile.path
431
+
432
+ return IO.read(metadata_tmpfile.path)
433
+
434
+ end
435
+
436
+ ## Return Nokogiri object tree for the metadata
437
+ ## @return [Nokogiri::XML::Document] Nokogiri document
438
+ def parse_xml
439
+
440
+ ## Parse the entire file as an XML document
441
+ doc = Nokogiri::XML.parse(content) do |config|
442
+ #config.strict.noent.dtdvalid
443
+ end
444
+
445
+ ## Select the top node
446
+ xml = doc.root
447
+
448
+ ## Add exotic namespaces to make sure we can deal with all metadata # TODO
449
+ NAMESPACES.each_pair { |label, uri| xml.add_namespace_definition(label,uri) }
450
+
451
+ return xml
452
+
453
+ end
454
+
455
+ def read
456
+
457
+ Nokogiri::XML::Reader(content)
458
+
459
+ end
460
+
461
+ ## Does the source object look sensible?
462
+ ## @return [TrueClass, FalseClass] True or false
463
+ def ok?
464
+
465
+ return false unless metadata_source and metadata_source.size > 1
466
+
467
+ return true
468
+
469
+ end
470
+
471
+ private
472
+
473
+
474
+ ## Logging
475
+ def log
476
+
477
+ return ::Shibkit::MetaMeta.config.logger
478
+
479
+ end
480
+
481
+
482
+ public
483
+
484
+ ##
485
+ ## Class Methods
486
+ ##
487
+
488
+ ## Load a metadata source list from a YAML file
489
+ ## @param [String] source_list Filesystem path of a sources YAML file or
490
+ ## :real for included list of real sources, :dev for mock sources, or
491
+ ## :auto for either :real or :dev, based on environment
492
+ ## @return [Array] Array of metadata source objects
493
+ def self.load(source_list=:auto, *selected_groups)
494
+
495
+ selected_groups = selected_groups.empty? ? ::Shibkit::MetaMeta.config.selected_groups : []
496
+
497
+ Shibkit::MetaMeta.log.debug "Load sources from #{source_list.to_s}"
498
+
499
+ file = self.locate_sources_file(source_list)
500
+
501
+ sources = Array.new
502
+ source_data = YAML::load(File.open(file))
503
+ Shibkit::MetaMeta.log.debug "Source YAML:\n#{source_data.inspect}"
504
+
505
+ ## Load records from the YAML-derived hash rather than directly to process them first
506
+ source_data.each_pair do |id, data|
507
+
508
+ case data
509
+ when Source
510
+ sources << data
511
+ when Hash
512
+ sources << Source.from_hash(data,id)
513
+ else
514
+ raise "Don't know how to convert #{source_data.class} to Source"
515
+ end
516
+ end
517
+
518
+ ## If groups are specified then trim off any non-matching sources
519
+ unless selected_groups.empty? or selected_groups.include? :all
520
+
521
+ Shibkit::MetaMeta.log.info "Filtering source/federations by selected groups"
522
+
523
+ selected_groups.inject([]){|m,v| m << v.to_s.downcase.to_sym }
524
+
525
+ group_set = Set.new selected_groups
526
+ sources = sources.delete_if { |s| group_set.intersection(s.groups).empty? }
527
+
528
+ Shibkit::MetaMeta.log.debug "Rejected sources that aren't in #{selected_groups.join(', ')}"
529
+
530
+ end
531
+
532
+ return sources
533
+
534
+ end
535
+
536
+ ## Return appropriate file path for
537
+ def self.locate_sources_file(source_list)
538
+
539
+ config = ::Shibkit::MetaMeta.config
540
+
541
+ case source_list
542
+ when :auto
543
+ file_path = config.in_production? ? REAL_SOURCES_FILE : DEV_SOURCES_FILE
544
+ when :dev, :test
545
+ file_path = DEV_SOURCES_FILE
546
+ when :real, :prod, :production
547
+ file_path = REAL_SOURCES_FILE
548
+ else
549
+ file_path = source_list
550
+ end
551
+
552
+ return file_path
553
+
554
+ end
555
+
556
+ end
557
+ end
558
+ end