shibkit-meta_meta 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +52 -0
- data/Icon.png +0 -0
- data/LICENSE.txt +177 -0
- data/README.md +789 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/examples/biggest_entity_id.rb +4 -0
- data/lib/shibkit/meta_meta.rb +600 -0
- data/lib/shibkit/meta_meta/attribute.rb +73 -0
- data/lib/shibkit/meta_meta/config.rb +463 -0
- data/lib/shibkit/meta_meta/contact.rb +85 -0
- data/lib/shibkit/meta_meta/data/default_metadata/example_federation_metadata.xml +168 -0
- data/lib/shibkit/meta_meta/data/default_metadata/local_metadata.xml +66 -0
- data/lib/shibkit/meta_meta/data/default_metadata/uncommon_federation_metadata.xml +115 -0
- data/lib/shibkit/meta_meta/data/default_metadata_cache.yml +166 -0
- data/lib/shibkit/meta_meta/data/dev_sources.yml +86 -0
- data/lib/shibkit/meta_meta/data/real_sources.yml +163 -0
- data/lib/shibkit/meta_meta/entity.rb +219 -0
- data/lib/shibkit/meta_meta/federation.rb +161 -0
- data/lib/shibkit/meta_meta/idp.rb +81 -0
- data/lib/shibkit/meta_meta/logo.rb +216 -0
- data/lib/shibkit/meta_meta/metadata_item.rb +244 -0
- data/lib/shibkit/meta_meta/mixin/cached_downloads.rb +127 -0
- data/lib/shibkit/meta_meta/mixin/xpath_chores.rb +111 -0
- data/lib/shibkit/meta_meta/organisation.rb +73 -0
- data/lib/shibkit/meta_meta/provider.rb +195 -0
- data/lib/shibkit/meta_meta/provisioning/base.rb +33 -0
- data/lib/shibkit/meta_meta/requested_attribute.rb +29 -0
- data/lib/shibkit/meta_meta/service.rb +94 -0
- data/lib/shibkit/meta_meta/source.rb +558 -0
- data/lib/shibkit/meta_meta/sp.rb +79 -0
- data/shibkit-meta_meta.gemspec +154 -0
- data/spec/meta_meta/attribute/token +0 -0
- data/spec/meta_meta/config/autoloading_and_refreshing_spec.rb +72 -0
- data/spec/meta_meta/config/code_nspec.rb +13 -0
- data/spec/meta_meta/config/configuration_spec.rb +30 -0
- data/spec/meta_meta/config/creation_spec.rb +43 -0
- data/spec/meta_meta/config/downloading_and_caching_settings_spec.rb +216 -0
- data/spec/meta_meta/config/env_platform_settings.rb +129 -0
- data/spec/meta_meta/config/filtering_settings_spec.rb +123 -0
- data/spec/meta_meta/config/init.rb +8 -0
- data/spec/meta_meta/config/logger_settings_spec.rb +91 -0
- data/spec/meta_meta/config/smartcache_settings_spec.rb +110 -0
- data/spec/meta_meta/config/source_file_settings_spec.rb +99 -0
- data/spec/meta_meta/config/tagging_settings_spec.rb +81 -0
- data/spec/meta_meta/config/working_directory_settings_spec.rb +106 -0
- data/spec/meta_meta/config/xml_processing_settings_spec.rb +75 -0
- data/spec/meta_meta/contact/contact_oldspec.rb +0 -0
- data/spec/meta_meta/entity/entity_oldspec.rb +53 -0
- data/spec/meta_meta/federation/federation_oldspec.rb +0 -0
- data/spec/meta_meta/idp/token +0 -0
- data/spec/meta_meta/logo/token +0 -0
- data/spec/meta_meta/meta_meta/cache_example.yaml +141284 -0
- data/spec/meta_meta/meta_meta/meta_meta_spec.rb +269 -0
- data/spec/meta_meta/meta_meta/saved_sources.yaml +46 -0
- data/spec/meta_meta/metadata_item/token +0 -0
- data/spec/meta_meta/organisation/organisation_oldspec.rb +0 -0
- data/spec/meta_meta/provider/token +0 -0
- data/spec/meta_meta/requested_attribute/token +0 -0
- data/spec/meta_meta/service/token +0 -0
- data/spec/meta_meta/source/application_extras_spec.rb +234 -0
- data/spec/meta_meta/source/conversion_spec.rb +75 -0
- data/spec/meta_meta/source/creation_spec.rb +0 -0
- data/spec/meta_meta/source/downloads_and_caching_spec.rb +0 -0
- data/spec/meta_meta/source/federation_information_spec.rb +11 -0
- data/spec/meta_meta/source/fixtures.rb +24 -0
- data/spec/meta_meta/source/init.rb +1 -0
- data/spec/meta_meta/source/loading_and_saving_spec.rb +0 -0
- data/spec/meta_meta/source/metadata_details_spec.rb +0 -0
- data/spec/meta_meta/source/metadata_integrity_spec.rb +0 -0
- data/spec/meta_meta/source/selection_spec.rb +0 -0
- data/spec/meta_meta/source/source_oldspec.rb +353 -0
- data/spec/meta_meta/source/xml_parsing_spec.rb +0 -0
- data/spec/meta_meta/sp/token +0 -0
- data/spec/meta_meta/template +2 -0
- data/spec/moi/config_spec.rb +0 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/supply_xml.rb +0 -0
- 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
|