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,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'nokogiri'
5
+ gem 'rest-client'
6
+ gem 'rest-client-components'
7
+ gem 'rack-cache'
8
+ gem 'addressable'
9
+ gem 'chunky_png'
10
+ gem 'dimensions'
11
+ gem 'json'
12
+
13
+
14
+ group :development do
15
+ gem "rspec", "~> 2.7.0"
16
+ gem "bundler", "~> 1.0.0"
17
+ gem "jeweler", "~> 1.6.2"
18
+ #gem "rcov", ">= 0"
19
+ # gem "httparty"
20
+ #gem 'reek'
21
+ end
@@ -0,0 +1,52 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.6)
5
+ chunky_png (1.2.5)
6
+ diff-lcs (1.1.3)
7
+ dimensions (1.0.0)
8
+ git (1.2.5)
9
+ jeweler (1.6.4)
10
+ bundler (~> 1.0)
11
+ git (>= 1.2.5)
12
+ rake
13
+ json (1.6.1)
14
+ json (1.6.1-java)
15
+ mime-types (1.17.2)
16
+ nokogiri (1.5.0)
17
+ nokogiri (1.5.0-java)
18
+ rack (1.3.5)
19
+ rack-cache (1.1)
20
+ rack (>= 0.4)
21
+ rake (0.9.2.2)
22
+ rest-client (1.6.7)
23
+ mime-types (>= 1.16)
24
+ rest-client-components (1.2.0)
25
+ rack (>= 1.0.1)
26
+ rest-client (>= 1.6.0, < 1.7.0)
27
+ rspec (2.7.0)
28
+ rspec-core (~> 2.7.0)
29
+ rspec-expectations (~> 2.7.0)
30
+ rspec-mocks (~> 2.7.0)
31
+ rspec-core (2.7.1)
32
+ rspec-expectations (2.7.0)
33
+ diff-lcs (~> 1.1.2)
34
+ rspec-mocks (2.7.0)
35
+
36
+ PLATFORMS
37
+ java
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ addressable
42
+ bundler (~> 1.0.0)
43
+ chunky_png
44
+ dimensions
45
+ jeweler (~> 1.6.2)
46
+ json
47
+ nokogiri
48
+ rack-cache
49
+ rake
50
+ rest-client
51
+ rest-client-components
52
+ rspec (~> 2.7.0)
Binary file
@@ -0,0 +1,177 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,789 @@
1
+ Shibkit::MetaMeta - Lazy Access To SAML Metadata
2
+ ================================================
3
+
4
+ ## DESCRIPTION
5
+
6
+ Shibkit::MetaMeta aims to provide lazy, friendly handling of
7
+ Shibboleth/SAML2 metadata. Easily download and parse metadata XML into Ruby
8
+ objects.
9
+
10
+ ### What is SAML Metadata? What is Shibboleth?
11
+ SAML2 Metadata is widely used in education to build access management federations
12
+ - groups of trusted IDPs (login servers) and SPs (websites using the IDPs for authentication).
13
+
14
+ MetaMeta is the first part of Shibkit to be released. It does not require any
15
+ of the other Shibkit gems to work but most of the others depend on it.
16
+
17
+ ### Why use Shibkit::MetaMeta?
18
+
19
+ There are few reasons to use MetaMeta if you are running an IDP or a simple
20
+ authenticated website. However, if you are building a more advanced web application
21
+ that needs to be aware of other entities in its federations then MetaMeta may be useful to you.
22
+
23
+ Features include:
24
+
25
+ * Ready-to-use configurations and information for all major SAML federations (not actually complete yet)
26
+ * Efficient download, caching and expiry of metadata, using ETag, Expires, Cache-Control and Last-Modified headers where available
27
+ * Validation of metadata (also not complete but we're working on it)
28
+ * Immediate access to metadata as XML or parsed Nokogiri documents
29
+ * Conversion of metadata into convenient Ruby objects for Federations, Entities, Contacts and Organisations.
30
+ * Easy storage of objects into databases for querying
31
+ * Compatibility with JRuby and Java scripting - it may then be included in Java IDPs and other Java applications (not working yet)
32
+ * Easy integration with non-Ruby applications: MetaMeta will act as a loader to build databases of entities for use by your Java, .Net, Python or PHP application.
33
+
34
+ ### Shibkit::Disco
35
+
36
+ Shibkit::MetaMeta provides a simple interface to data stored in SAML metadata files
37
+ but does not include any persistence or query facilities, discovery protocol support, etc.
38
+
39
+ If you are planning to use SAML metadata within your application you might be better
40
+ using Shibkit::Disco, a library that builds on Shibkit::MetaMeta to provide a
41
+ SAML discovery framework. Most features of Shibkit::MetaMeta will still be available inside
42
+ Shibkit::Disco.
43
+
44
+ >I feel the same way about disco as I do about herpes.
45
+ >*Hunter S. Thompson*
46
+
47
+ If you don't fancy the heavier framework in Shibkit::Disco you can probably get
48
+ what you need from Shibkit::MetaMeta, or use it in a framework of your own.
49
+
50
+ ## CAVEATS
51
+
52
+ MetaMeta is still early in development so please bear the following in mind when using it:
53
+
54
+ * Tests and API documentation are not complete. They will be completed before version 1.0.0.
55
+ * The API may not be stable until version 1.0. If using Bundler please lock the version to avoid upgrades breaking your application
56
+ * Full validation of metadata is not present yet. **Do not use MetaMeta for security checks yet.**
57
+ * The mock 'dev' metadata is not valid or complete. We plan to eventually build some fully-functional example federations, but at present both UnCommon and Example federations are simple test mocks of certain parts of SAML2 metadata.
58
+ * The source list of federations is _far_ from complete.
59
+ * For development and testing the provided lists should be fine but please DO NOT use the provided federation source lists in production without manually checking their contents or using your own edited version. Your federation will have its own guidelines for verifying their certificate and metadata, please read them and check that the certificate and source URL you are using are correct. Your chain of trust should not originate in a file on Github, even if the creators are nice people.
60
+ * MetaMeta is using far too much memory when processing metadata XML.
61
+ * MetaMeta is not compatible with JRuby yet (but we hope it will be)
62
+ * Not yet tested on Windows, although it detects Windows and tries to compensate.
63
+
64
+ ## INSTALLATION
65
+
66
+ Please note: we haven't actually released the gem yet! It's best to download
67
+ the source from Github and then run `rake install`
68
+
69
+ ### Requirements
70
+ Shibkit::MetaMeta is available as a gem and should bring in most dependencies itself
71
+ when installed. It does need a few of other things as well:
72
+
73
+ * Ruby 1.8.7+ or Ruby 1.9.2+
74
+ * Rubygems (not required but makes things much simpler)
75
+ * LibXML2 (including LibXML2-devel on Linux)
76
+ * Linux, Mac OS, *BSD, Solaris (Windows is unsupported at present)
77
+
78
+ ### As a released Ruby Gem
79
+
80
+ #### Rubygems
81
+
82
+ If you use RubyGems directly then simply type:
83
+
84
+ ```bash
85
+ gem install shibkit-meta_meta
86
+ ```
87
+
88
+ and require the gem in your code
89
+
90
+ ```ruby
91
+ require 'shibkit/meta_meta'
92
+ ```
93
+
94
+ #### Bundler
95
+
96
+ Bundler users can add MetaMeta to their Gemfiles like this:
97
+
98
+ ```ruby
99
+ source "http://rubygems.org"
100
+ gem "shibkit-meta_meta"
101
+ ```
102
+
103
+ then of course run `bundle install` on the commandline and
104
+ `require 'bundler'` within your code.
105
+
106
+ It's a very good idea to immediately specify a gem version since MetaMeta is
107
+ going to a little unstable for awhile, and there may be breaking-things API
108
+ changes until v1.0.0.
109
+
110
+ ### Using the latest development version
111
+ If you'd like to use the very latest in-development version of MetaMeta, possibly
112
+ as a developer you should check it out of Github and include it with Bundler by specifying
113
+ the source location:
114
+
115
+ ```bash
116
+ git clone git@github.com:Digital-Identity-Labs/shibkit-meta_meta.git
117
+ ```
118
+
119
+ ```ruby
120
+ source "http://rubygems.org"
121
+ gem "shibkit-meta_meta", :path => "~/Projects/shibkit-meta_meta/"
122
+ ```
123
+
124
+ You can also skip Bundler and install the gem directly from source by typing
125
+
126
+ ```shell
127
+ rake install
128
+ ```
129
+
130
+ Please feel welcome to fork the project on Github and send pull requests for any
131
+ changes you wish to contribute.
132
+
133
+ ## USAGE
134
+
135
+ ### Convenience Features
136
+
137
+ The MetaMeta class provides a number of simple factory-style methods to return
138
+ simplified representations of the items within SAML metadata files.
139
+
140
+ #### Automatic metadata retrieval and parsing
141
+
142
+ MetaMeta contains information about a number of popular federations which it
143
+ will access by default. While it's best to use your own list of sources, it be
144
+ convenient to get started immediately.
145
+
146
+ For instance, to find the longest entity URI in all popular federations all you
147
+ need is:
148
+
149
+ ```ruby
150
+ puts Shibkit::MetaMeta.entities.sort!{|a,b| a.uri.size <=> b.uri.size}.last
151
+ ```
152
+
153
+ Metadata will normally be automatically downloaded, cached, parsed and sorted on first use.
154
+
155
+ #### Easy access to Federation, Entities and Organisations
156
+ MetaMeta can return arrays of all federations, and all entities (SPs and IDPs),
157
+ IDPs or SPs in all Federations. It can also attempt to list all organisations but
158
+ the data returned is not yet particularly useful.
159
+
160
+ ```ruby
161
+ Shibkit::MetaMeta.federations.each {|f| puts f }
162
+ Shibkit::MetaMeta.entities.each { |e| puts e }
163
+ Shibkit::MetaMeta.idps.each { |e| puts e }
164
+ Shibkit::MetaMeta.sps.each { |e| puts e }
165
+ Shibkit::MetaMeta.orgs.each { |o| puts o }
166
+ ```
167
+
168
+ #### Select an entity by URI
169
+ If you already know the URI of an entity in a loaded federation then you can get it directly using
170
+ `#from_uri`.
171
+
172
+ ```ruby
173
+ entity = Shibkit::MetaMeta.from_uri('https://shib.manchester.ac.uk/shibboleth')
174
+
175
+ puts entity.idp?
176
+ puts entity.accountable?
177
+ ```
178
+
179
+ Read more about the Shibkit::MetaMeta class
180
+
181
+ ----
182
+
183
+ ### Metadata Sources
184
+
185
+ MetaMeta needs to know various things about a Federation before it can access
186
+ its metadata. This information is handled by the Source class. Sources can be
187
+ specified directly in your code or loaded from a source list file.
188
+
189
+ Source objects describe federations and collections and are effectively metadata
190
+ about metadata, hence the odd name of this software.
191
+
192
+ #### Loading your own source list
193
+ It's best to write and load your own source list for your software.
194
+
195
+ * You can download and process only the federations you require, saving time and
196
+ energy.
197
+ * You can check that file locations, certificates and fingerprints are correct
198
+ * You can include your own federations or simpler local metadata collections
199
+
200
+ Source lists are simple YAML documents. Copy the examples included with MetaMeta
201
+ or read the Shibkit::MetaMeta::Source documentation before writing your own.
202
+
203
+ ```ruby
204
+ Shibkit::MetaMeta.config.sources_file = '/etc/mm/my_metadata_sources.yml'
205
+ Shibkit::MetaMeta.idps.each { |e| puts e }
206
+ ```
207
+
208
+ #### Selecting a built-in source list
209
+ MetaMeta comes with a few source lists that you can choose from.
210
+
211
+ * `:real` is a list of all major federations (this file may not be complete yet!)
212
+ * `:dev` is a small list of tiny fictional federations for testing and development
213
+
214
+ They are loaded by specifying the symbol instead of a real filename string
215
+
216
+ ```ruby
217
+ Shibkit::MetaMeta.config.sources_file = :real
218
+ Shibkit::MetaMeta.idps.each { |e| puts e }
219
+ ```
220
+
221
+ ----
222
+
223
+ ### Accessing Source information
224
+ Source objects can be accessed directly, to be read or adjusted after loading.
225
+
226
+ ```ruby
227
+ ## List homepages specified in all metadata sources, wherever the source is defined
228
+ Shibkit::MetaMeta.sources.each {|s| puts s.homepage_url}
229
+
230
+ ```
231
+
232
+ #### Automatic selection of source lists
233
+ By default MetaMeta will choose a (hopefully) suitable source list for you.
234
+
235
+ (At the moment this is always the `:real` list but when the `:dev` list is fixed this will
236
+ be used instead when in 'development' mode in Rails and Sinatra applications)
237
+
238
+ #### Automatic loading and processing of Metadata
239
+ MetaMeta will normally load and process XML from your sources when you first
240
+ ask for data. However, this can sometimes cause delays exactly when you don't want them.
241
+
242
+ Autoloading of metadata can be turned off and on at any time:
243
+
244
+ ```ruby
245
+ Shibkit::MetaMeta.config.autoload = false
246
+ # or maybe
247
+ Shibkit::MetaMeta.config.autoload = true if Date.today.day == 1
248
+ ```
249
+
250
+ Of course if autoloading is turned off you'll not get any federations or entities
251
+ when you need them. To load data call the `#load_sources` method:
252
+
253
+ ```ruby
254
+ Shibkit::MetaMeta.load_sources # Loads sources file but not actual metadata
255
+ Shibkit::MetaMeta.process_sources # Downloads and processes metadata into objects
256
+
257
+ # There will now be a delay while metadata is downloaded and processed...
258
+
259
+ Shibkit::MetaMeta.loaded_sources? # => true
260
+ Shibkit::MetaMeta.stocked? # => true
261
+ ```
262
+
263
+ You can call `Shibkit::MetaMeta.process_sources` to pre-emptively create objects
264
+ even if `Shibkit::MetaMeta.config.autoload` is active.
265
+
266
+ #### Adding your own Sources without a sources file
267
+
268
+ It's possible to append additional sources using the `Shibkit::MetaMeta.add_source`
269
+ method. Pass either a hash or a Source object you prepared earlier.
270
+
271
+ If a source with the same ID URI already exists then it will be replaced by the
272
+ new one.
273
+
274
+ ```ruby
275
+ Shibkit::MetaMeta.add_source({
276
+ :uri => 'http://ukfederation.org.uk',
277
+ :name => 'UK Access Management Federation For Education And Research',
278
+ :display_name => 'UK Access Management Federation',
279
+ :type => 'federation',
280
+ :countries => ['gb'],
281
+ :metadata => 'http://metadata.ukfederation.org.uk/ukfederation-metadata.xml',
282
+ :certificate => 'http://metadata.ukfederation.org.uk/ukfederation.pem',
283
+ :fingerprint => '94:7F:5E:8C:4E:F5:E1:69:E7:DF:68:1E:48:AA:98:44:A5:41:56:EE',
284
+ :refeds_info => 'https://refeds.terena.org/index.php/FederationUkfed',
285
+ :homepage => 'http://www.ukfederation.org.uk',
286
+ :languages => ['en-gb', 'en'],
287
+ :support_email => ' service@ukfederation.org.uk',
288
+ :description => 'A single solution for accessing online resources and services',
289
+ })
290
+
291
+
292
+ ```
293
+
294
+ ----
295
+
296
+ ### Federations
297
+
298
+ Federation objects describe a federation, including its members
299
+ (SPs and IDPs).
300
+
301
+ #### Listing federation objects
302
+
303
+ You can get an array of all loaded federations using '#federations'
304
+
305
+ ```ruby
306
+ all_federations = Shibkit::MetaMeta.federations
307
+ ```
308
+
309
+ #### Finding an federation by URI
310
+
311
+ ```ruby
312
+ uk_fed = Shibkit::MetaMeta.from_uri('http://ukfederation.org.uk')
313
+ ```
314
+
315
+ #### Loading federation objects ahead of time
316
+
317
+ Calling `Shibkit::MetaMeta.process_sources` will re-process federation metadata
318
+ into objects, removing previously generated objects from MetaMeta's lists.
319
+
320
+ ```ruby
321
+ Shibkit::MetaMeta.process_sources
322
+ Shibkit::MetaMeta.stocked? # => true
323
+ ```
324
+
325
+ #### Filtering and selecting Sources/Federations
326
+
327
+ It's nice to have easy access to many different federations but if you're only
328
+ interested in one or two of them it can add a lot of wasteful overhead to process
329
+ all of them together.
330
+
331
+ You can limit the sources/federations to be processed using the `#only_use` method:
332
+
333
+ ```ruby
334
+ Shibkit::MetaMeta.config.only_use(['http://ukfederation.org.uk'])
335
+
336
+ ```
337
+
338
+ After specifying federation URIs only matching federations will be downloaded
339
+ and processed.
340
+
341
+ You can go back to processing all federations by using `:all` or `:everything`
342
+
343
+ ```ruby
344
+ Shibkit::MetaMeta.config.only_use(:everything)
345
+
346
+ ```
347
+
348
+ ----
349
+
350
+ ### Entities (IDPs & SPs)
351
+
352
+ Entity objects are generic representations of entities listed in SAML metadata.
353
+ They can be IDPs, SPs, or both. MetaMeta only stores general information about the
354
+ entity in the Entity object itself, and then uses SP and IDP objects inside it
355
+ to store more detailed information about the roles of the entity.
356
+
357
+ #### Accessing all the entities in a federation
358
+
359
+ ```ruby
360
+ federation = Shibkit::MetaMeta.from_uri('http://ukfederation.org.uk')
361
+ uk_fed_entities = federation.entities
362
+ ```
363
+
364
+ #### Finding an entity by URI
365
+
366
+ ```ruby
367
+ uom_idp = Shibkit::MetaMeta.from_uri('https://shib.manchester.ac.uk/shibboleth')
368
+ ```
369
+
370
+ #### Listing all primary entities in all federations
371
+ If you're read this far you can probably guess how this will go.
372
+
373
+ ```ruby
374
+ all_entities = Shibkit::MetaMeta.entities
375
+ ```
376
+
377
+ This doesn't list *all* entities, only all *primary* entities. I'm afraid we've made
378
+ up the term "primary entity". Read on for enlightenment.
379
+
380
+ #### Multi-federation entities and primary entities
381
+
382
+ The same IDP or SP, represented by the same URI, can be in more than one federation.
383
+ The Shibboleth IDP will load the only the first one that it finds. Because of this,
384
+ although it's possible to give the same service different metadata in each
385
+ federation it's probably a bad idea to do so - you don't know which metadata
386
+ for your service will be used.
387
+
388
+ IDPs and SPs don't usually care which trusted federation an entity belongs to - they're
389
+ trusted, and that's what matters. However, your SAML-aware software might care, so
390
+ MetaMeta tries to keep track of multiple federation membership.
391
+
392
+ Each Federation object has its own list of entities. These are separate objects and if
393
+ metadata for a service varies between different federations it should be different
394
+ between their MetaMeta objects too.
395
+
396
+ ```ruby
397
+ entity1 = fed1.entities.collect { |e| e.uri = 'http://silly-idp.com/shib' }[0]
398
+ entity2 = fed2.entities.collect { |e| e.uri = 'http://silly-idp.com/shib' }[0]
399
+
400
+ entity1.primary_federation_uri # => 'http://fed1.org'
401
+ entity1.idp.protocols
402
+ # => ['urn:oasis:names:tc:SAML:1.1:protocol', 'urn:oasis:names:tc:SAML:2.0:protocol']
403
+
404
+ entity2.primary_federation_uri # => 'http://fed2.org'
405
+ entity2.idp.protocols
406
+ # => ['urn:oasis:names:tc:SAML:2.0:protocol']
407
+
408
+ ```
409
+
410
+ In most cases you don't want to know about the other varieties; you want to know about
411
+ the entity with the first metadata to be loaded. Shibkit::MetaMeta refers to this
412
+ as the "Primary Entity", and its parent Federation as its "primary federation".
413
+
414
+ Shibkit::MetaMeta only lists primary records when you call `Shibkit::MetaMeta.from_uri` and
415
+ `Shibkit::MetaMeta.entities`
416
+
417
+ Primary entities will have any other federations that they are a member of listed under
418
+ `#other_federations` and `#secondary_federations`. Both primary and seconday/other federations are
419
+ listed by `#federation_uris`.
420
+
421
+ You can quickly check if an entity is primary or multifederation:
422
+
423
+ ```ruby
424
+ ent = Shibkit::MetaMeta.from_uri('https://idp.uni.ac.uk/shibboleth')
425
+
426
+ ent.primary? # => true
427
+ ent.multi_federated? # => true
428
+ ```
429
+
430
+ #### Entity objects
431
+
432
+ ```ruby
433
+ ent = Shibkit::MetaMeta.from_uri('https://idp.uni.ac.uk/shibboleth')
434
+
435
+ ent.uri # => 'https://idp.uni.ac.uk/shibboleth'
436
+ ent.accountable? # => true
437
+ ent.hide? # => false
438
+ ent.sp? # => false
439
+ ent.idp? # => true
440
+
441
+ ent.idp.scopes # => ['uni.ac.uk']
442
+
443
+ ```
444
+
445
+ For more information on Entity objects please read the API documentation.
446
+
447
+ ----
448
+
449
+ ### IDPs
450
+
451
+ While an Entity object can represent an IDP (indicated by the `#idp?` method) the details of its
452
+ IDP role are held in the IDP object it contains.
453
+
454
+ ```ruby
455
+ entity.idp? # => true
456
+ entity.idp.protocols # => ['urn:oasis:names:tc:SAML:2.0:protocol']
457
+ entity.idp.scopes # => ['uni.ac.uk']
458
+
459
+ entity.idp.display_name # => "The University of Studies"
460
+ entity.idp.display_name :fr # => "L'Université des Etudes"
461
+ entity.idp.description # => "Example login service for UoS"
462
+ entity.idp.domains # => ['uni.ac.uk']
463
+ ```
464
+
465
+ For more information on IDP objects please read the API documentation.
466
+
467
+ ----
468
+
469
+
470
+ ### SPs
471
+
472
+ Entity objects can also represent an SP (even if also an IDP). As with IDPs the
473
+ additional information is represented by an SP object within the Entity object.
474
+
475
+ ```ruby
476
+ entity.sp? # => true
477
+ entity.sp.protocols # => ['urn:oasis:names:tc:SAML:2.0:protocol']
478
+
479
+ entity.sp.display_name # => "University of Studies Webmail"
480
+ entity.sp.display_name :fr # => "L'Université des Etudes de Webmail"
481
+ entity.sp.description # => "Email service for staff and students"
482
+ entity.sp.ip_blocks # => ['192.168.1.0/24', '10.50.1.0/24']
483
+ ```
484
+
485
+ For more information on SP objects please read the API documentation.
486
+
487
+ ----
488
+
489
+ ### Contacts
490
+
491
+ IDP and SP objects may contain Contact objects. Each type of contact is available
492
+ from its own method in an Entity object. If a contact type is not available then `nil`
493
+ is returned.
494
+
495
+ ```ruby
496
+ sc = entity.support_contact
497
+ tc = entity.technical_contact
498
+ ac = entity.admin_contact
499
+ ```
500
+
501
+ Contact objects are very simple:
502
+
503
+ ```ruby
504
+ contact = entity.support_contact
505
+
506
+ contact.givenname # => 'Joe'
507
+ contact.surname # => 'Yossarian'
508
+ contact.display_name # => 'Joe Yossarian'
509
+ contact.email_url # => "mailto:joe.yossarian@uni.ac.uk"
510
+ contact.email_address # => 'joe.yossarian@uni.ac.uk'
511
+ contact.category # => :support
512
+
513
+ ```
514
+
515
+ For more information on Contact objects please read the API documentation.
516
+
517
+ ----
518
+
519
+ ### Organisations
520
+
521
+ IDP and SP objects may contain Organisation objects. Organisation details have
522
+ often been used to describe services (especially IDPs) rather than the organisation
523
+ running the service. Recent additions to SAML metadata (described in the next section) will
524
+ hopefully lead to organisation details gradually becoming more useful.
525
+
526
+ ```ruby
527
+ org = entity.organisation
528
+
529
+ org.name # => 'University of Studies'
530
+ org.display_name # => 'University of Studies IDP (test)'
531
+ org.url # => 'uni.ac.uk'
532
+ ```
533
+
534
+ MetaMeta will fall back to using Organisation data to describe entities when no
535
+ user interface information is available.
536
+
537
+ The `Shibkit::MetaMeta.orgs` method will list all organisations found in all federations
538
+ after trying, rather badly, to minimise repeated records. We added this feature to
539
+ see how well it would work, and so far it doesn't work very well at all.
540
+
541
+ For the curious or optimistic:
542
+
543
+ ```ruby
544
+ messy_list_organisations = Shibkit::MetaMeta.orgs
545
+ ```
546
+
547
+ ----
548
+
549
+ ### User Interface Info
550
+
551
+ Shibkit::MetaMeta aims to reproduce the details that Shibboleth IDPs can present
552
+ to users during authentication - friendly, localised information on SPs and IDPs.
553
+
554
+ User interface information for SPs and IDPs is available as as English default, a
555
+ specified locale (if available, falling back to English) and as a hash of all available
556
+ content.
557
+
558
+ ```ruby
559
+ entity.idp.display_name # => "The University of Studies"
560
+ entity.idp.display_name :fr # => "L'Université des Etudes"
561
+ entity.idp.display_names
562
+ # => {:en => "The University of Studies", :fr => "L'Université des Etudes"}
563
+
564
+ entity.idp.keywords # => ['example', 'university']
565
+ entity.idp.keywords :fr # => ['exemple', 'université']
566
+ entity.idp.keyword_sets
567
+ # => {:en => ['example', 'university'], :fr => ['exemple', 'université']}
568
+ ```
569
+
570
+ ----
571
+
572
+ ### Logos
573
+
574
+ SPs and IDPs may also have Logo objects, for use in user interfaces or just to
575
+ decorate your federation reports. Like user interface information they can be
576
+ grouped according to language, and the defaults assume `:en`
577
+
578
+ ```ruby
579
+ default_logos = entity.idp.logos
580
+ french_logos = entity.idp.logos :fr
581
+
582
+ default_logos.each { |logo| puts logo.url ; puts logo.width }
583
+
584
+ ## Find location of largest image in a set
585
+ french_logos.sort{ |a,b| a.pixels <=> b.pixels }.last.uri
586
+
587
+ ```
588
+
589
+ Logo objects have various methods for describing the image, downloading it,
590
+ comparing the real image to details in metadata, etc. Please read the API docs
591
+ for more info.
592
+
593
+ ----
594
+
595
+ ### Discovery Hints
596
+
597
+ Discovery hints can be used by WAYF/Discovery Services to guess at likely
598
+ options for users.
599
+
600
+ ```ruby
601
+ entity.sp.ip_blocks # => ['192.168.1.0/24', '10.50.1.0/24']
602
+ entity.sp.domains # => ['uni.ac.uk']
603
+ entity.idp.geo_location_uris # => nil
604
+ ```
605
+
606
+ ----
607
+
608
+
609
+ ### Service Information
610
+ SPs can advertise a number of Services.
611
+
612
+ ```ruby
613
+
614
+ entity.sp.services # Returns an array of service objects
615
+ service = entity.sp.default_service # Returns one service
616
+
617
+ service.attributes # Array of attribute objects
618
+ service.index # Returns the index number of the service
619
+ service.description # Default description
620
+ service.description :en # English description
621
+ service.description # Hash of all descriptions, keyed on language.
622
+
623
+ ```
624
+
625
+ ### IDP and SP Attributes
626
+ While rarely used, it's possible for metadata to list the attributes made available
627
+ by IDPs or requested by SPs. These should be available via the SP and IDP objects.
628
+
629
+ ```ruby
630
+ entity.idp.attributes.each { |a| puts a.friendly_name }
631
+
632
+ entity.sp.default_service.attributes.each { |a| puts a.name }
633
+
634
+ attribute = entity.idp.attributes[0]
635
+
636
+ attribute.name # Name uri for the attribute
637
+ attribute.required? # (Used by SPs)
638
+ attribute.name_format #
639
+ attribute.friendly_name #
640
+ attribute.values # Available values (from IDPs)
641
+
642
+ ```
643
+
644
+ ----
645
+
646
+ ### Provisioning Your Application
647
+
648
+ Shibkit::MetaMeta is **not** a sleek and speedy bit of software. It can use a
649
+ fairly large amount of RAM to process metadata - when loading four federations
650
+ (c.2400 unique entities) a couple of hundred megabytes of RAM is typical,
651
+ and also takes about a minute on a typical PC.
652
+
653
+ Because of this it is probably a very bad idea to autoload objects inside a
654
+ persistent web application, especially at startup. Using multiple Mongrel processes,
655
+ each loading their own metadata, is definitely not advisable.
656
+
657
+ Shibkit::MetaMeta is best suited to running scripts that process metadata then quit,
658
+ maybe loading data into other storage formats.
659
+
660
+ If you want a persistent database to query within your applications you should
661
+ consider Shibkit::Disco, which builds on MetaMeta and provides a variety of database
662
+ backends.
663
+
664
+ ----
665
+
666
+ ### Optimising for speed or memory
667
+
668
+ There are a few things that can make Shibkit:MetaMeta a little lighter.
669
+
670
+ #### Speed: Smartcache
671
+
672
+ MetaMeta's "smartcache" feature will store the objects created from your metadata
673
+ XML on disk and reload them the next time MetaMeta is run. A simple script that takes two
674
+ minutes to download and process metadata when first run may take a couple of seconds
675
+ the second time it's run if you have the smartcache enabled.
676
+
677
+ Smartcaching is on by default, but can be turned off with Shibkit::MetaMeta::Config's
678
+ ```#smartcache_active``` method. Smartcache data expires when it's older than the
679
+ ```#smartcache_expiry``` config setting or when MetaMeta's configuration,
680
+ platform or Ruby version changes.
681
+
682
+ ```ruby
683
+ Shibkit::MetaMeta.config do |c|
684
+ c.autoload = true
685
+ c.smartcache_expiry = 60*60
686
+ c.smartcache_active = true
687
+ end
688
+ ```
689
+
690
+ If the smartcache is turned off then metadata files will be downloaded each time
691
+ your script is run. Download caching will still occur if possible.
692
+
693
+ #### Memory: Purging XML
694
+
695
+ MetaMeta objects will normally store the parsed XML used to create them. This can
696
+ take up more RAM and will cause larger, slower smartcache files. In many cases you won't
697
+ need this data at all after the object has been created, so you can safely delete it.
698
+
699
+ ```ruby
700
+ Shibkit::MetaMeta.config do |c|
701
+ c.purge_xml = true
702
+ end
703
+ ```
704
+
705
+ ----
706
+
707
+ ### General Options
708
+
709
+ [There are a few methods in the Source class that need to be refactored out for use everywhere
710
+ in MetaMeta. Should be fixed soon.]
711
+
712
+ ----
713
+
714
+ ### Download Caching Options
715
+
716
+ ...
717
+
718
+ ### Debugging Hints
719
+
720
+ You can dump all objects in a readable format using ```#save_cache_file``` and
721
+ reload them using ```#load_cache_file```. (This can use quite a bit of RAM)
722
+
723
+ ```ruby
724
+ Shibkit::MetaMeta.save_cache_file "/Users/pete/Desktop/dump.yaml"
725
+ # ...
726
+ Shibkit::MetaMeta.load_cache_file "/Users/pete/Desktop/dump.yaml"
727
+ ```
728
+
729
+ ## BACKGROUND READING
730
+
731
+ * [Wikipedia on SAML2](http://en.wikipedia.org/wiki/SAML_2.0)
732
+ * [Shibboleth software](http://shibboleth.internet2.edu/)
733
+ * [SAML2 Metadata spec](http://www.oasis-open.org/committees/download.php/35391/sstc-saml-metadata-errata-2.0-wd-04-diff.pdf)
734
+ * [Metadata UI spec](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf)
735
+ * [Metadata Correctness](https://wiki.shibboleth.net/confluence/display/SHIB2/MetadataCorrectness)
736
+ * [The SAML2 Spec](http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf)
737
+ * [All the SAML2 specs](http://saml.xml.org/saml-specifications)
738
+
739
+ ## SHIBKIT
740
+
741
+ Shibkit::MetaMeta is part of Shibkit, a collection of Ruby gems derived from
742
+ code used by Digital Identity Ltd in various projects and experiments. Most
743
+ future commercial and open source Digital Identity Labs projects will be based
744
+ on Shibkit libraries.
745
+
746
+ ## CONTRIBUTORS
747
+
748
+ * Pete Birkinshaw
749
+ * Eddy Wheldon
750
+ * Linda Ward
751
+ * Sam Jones
752
+
753
+ ## LICENSE
754
+
755
+ Shibkit is copyright (c) 2011, Digital Identity Ltd. It's licensed under the
756
+ Apache License, Version 2.0, which is a lot like the BSD and MIT licenses only
757
+ with added cunning bits and extra words for lawyers. It's a permissive license.
758
+
759
+ See [LICENSE.md](https://github.com/Digital-Identity-Labs/shibkit-meta_meta/blob/master/LICENSE)
760
+ for the entire whole license text if you're curious.
761
+
762
+ ## DIGITAL IDENTITY LABS
763
+
764
+ ...
765
+
766
+ ## OOPS...
767
+
768
+ We've definitely made mistakes. This is software - there are going to be coding bugs,
769
+ inaccurate documentation, misinterpreted specs, and horrible, embarrassing things that
770
+ we haven't even worried about yet.
771
+
772
+ If you find something wrong, weird or confusing please let us know on the Shibkit-MetaMeta
773
+ issue tracker. It might cause us blushes but we'd rather someone let us know straight away.
774
+
775
+ https://github.com/Digital-Identity-Labs/shibkit-meta_meta/issues
776
+
777
+ ## CONTRIBUTE
778
+
779
+ If you'd like to add new features to MetaMeta or even remove feature you hate
780
+ then please fork the repository on Github. Change the code to work the way you want,
781
+ and then send us a pull request if you'd like us to merge any of your changes back
782
+ into the official repository.
783
+
784
+ https://github.com/Digital-Identity-Labs/shibkit-meta_meta
785
+
786
+ If you'd like us to add a feature for you then please get in touch.
787
+
788
+
789
+