saml-sp 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ 3.1.2 / 2010-06-39
2
+ ---
3
+
4
+ * OSS release (Yay!)
5
+
6
+ 3.0.4 / 2010-04-26
7
+ ---
8
+
9
+ * Improved error messages when unable to interpret assertions documents properly.
10
+ * Improved debug logging around parsing assertion documents.
11
+
12
+ 3.0.2 / 2010-04-26
13
+ ---
14
+
15
+ * Fixed ArtifaceResolve request document such that it is valid against
16
+ the SAML schema.
17
+
18
+ 3.0.0 / 2010-04-23
19
+ ---
20
+
21
+ * Added way to specify both the IdP and SP identifiers in the config file.
22
+
23
+ 2.1.0 / 2010-03-26
24
+ ----
25
+
26
+ * Improved logging of artifact resolver registration.
27
+ * Promiscuous basic auth support.
28
+
29
+ 2.0.0 / 2010-01-29
30
+ ----
31
+
32
+ * Added issuer verification to artifact resolution to protect against
33
+ spoofing.
34
+ * Removed attempt to load config file from non-existent directory for
35
+ Rails apps.
36
+
37
+ 1.0.0 / 2010-01-22
38
+ ------
39
+
40
+ * 1 major enhancement
41
+ * Birthday!
42
+
43
+
@@ -0,0 +1,21 @@
1
+
2
+ (The MIT License)
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ 'Software'), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,126 @@
1
+ saml-sp
2
+ ====
3
+
4
+ by OpenLogic
5
+ http://openlogic.com
6
+ peter.williams@openlogic.com
7
+
8
+ ## STATUS:
9
+
10
+ This library is stable and under active development.
11
+
12
+ Version identifiers follow
13
+ [rational version ](http://docs.rubygems.org/read/chapter/7#page26).
14
+ The major version number does not indicate this library is complete.
15
+
16
+ ## DESCRIPTION:
17
+
18
+ Support for being a SAML 2.0 service provider in an HTTP artifact
19
+ binding SSO conversation.
20
+
21
+ ## SYNOPSIS:
22
+
23
+ This library provides parsing of SAML 2.0 artifacts. For example.
24
+
25
+ artifact = Saml2::Type4Artifact.new_from_string(params['SAMLart']) # => #<Saml2::Type4Artifact ...>
26
+ artifact.source_id # => 'a314Xc8KaSd4fEJAd8R'
27
+ artifact.type_code # => 4
28
+
29
+ Once you have an artifact you can resolve it into it's associated assertion:
30
+
31
+ assertion = artifact.resolve # => #<Saml2::Assertion>
32
+
33
+ With the assertion you can identify the user and retrieve attributes:
34
+
35
+ assertion.subject_name_id # => '1234'
36
+ assertion['mail'] # => 'john.doe@idp.example'
37
+
38
+ ### Configuration
39
+
40
+ If you are using Rails the SamlSp will automatically load
41
+ configuration info from `config/saml_sp.conf`.
42
+
43
+ For non-Rails apps the saml-sp configuration file can be place in the
44
+ application configuration directory and loaded using the following
45
+ code during application startup.
46
+
47
+ SamlSp::Config.load_file(APP_ROOT + "/config/saml_sp.conf")
48
+
49
+ #### Logging
50
+
51
+ If you are using saml-sp in a rails app it will automatically log to
52
+ the Rails default logger. For non-Rails apps you can specify a Logger
53
+ object to be used in the config file.
54
+
55
+ logger MY_APP_LOGGER
56
+
57
+
58
+ #### Artifact Resolution Service
59
+
60
+ For artifact resolution to take place you need to configure an
61
+ artifact resolution service for the artifacts source. This is done by
62
+ adding block similar to the following to your saml-sp config file.
63
+
64
+ artifact_resolution_service {
65
+ source_id 'opaque-id-of-the-idp'
66
+ uri 'https://samlar.idp.example/resolve-artifact'
67
+ identity_provider 'http://idp.example/'
68
+ service_provider 'http://your-domain.example/'
69
+ http_basic_auth {
70
+ realm 'the-idp-realm'
71
+ user_id 'my-user-id'
72
+ password 'my-password'
73
+ }
74
+ }
75
+
76
+ The configuration details are:
77
+
78
+ * source_id:
79
+ The id of the source that this resolution service can
80
+ resolve. This is a 20 octet binary string.
81
+
82
+ * uri:
83
+ The endpoint to which artifact resolve requests should be sent.
84
+
85
+ * identity_provider:
86
+ The URI identifying the identity provider that issues assertions
87
+ using the source id specified.
88
+
89
+ * service_provider:
90
+ The URI identifying the your software (the service provider) to
91
+ the identity provider.
92
+
93
+ * http_basic_auth:
94
+ (Optional) The credentials needed to authenticate with the IdP
95
+ using HTTP basic authentication.
96
+
97
+ #### Promiscuous Auth
98
+
99
+ If the IdP does not provide proper HTTP challenge responses you can
100
+ specify the HTTP auth in promiscuous mode. For example,
101
+
102
+ http_basic_auth {
103
+ promiscuous
104
+ user_id 'my-user-id'
105
+ password 'my-password'
106
+ }
107
+
108
+ In promiscuous mode the credentials are sent with every request to
109
+ this resolutions service regardless of it's realm.
110
+
111
+
112
+ ## REQUIREMENTS:
113
+
114
+ * Nokogiri
115
+ * Resourcful
116
+ * uuidtools
117
+
118
+ ## INSTALL:
119
+
120
+ * sudo gem install saml-sp
121
+
122
+ ## LICENSE:
123
+
124
+ Copyright (c) 2010 OpenLogic
125
+
126
+ Licensed under the MIT License. See LICENSE.txt
@@ -0,0 +1,29 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = 'saml-sp'
5
+ gemspec.summary = 'SAML 2.0 SSO Sevice Provider Library'
6
+ gemspec.email = 'pezra@barelyenough.org'
7
+ gemspec.authors = ["OpenLogic", "Peter Williams"]
8
+ gemspec.add_dependency 'nokogiri'
9
+ gemspec.add_dependency 'openlogic-resourceful'
10
+ gemspec.add_dependency 'uuidtools'
11
+ gemspec.add_development_dependency 'rspec'
12
+ gemspec.files = FileList["[A-Z]*", "{bin,generators,lib,test,spec,rails}/**/*"]
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new
21
+
22
+ task 'test:run' => :spec
23
+ # EOF
24
+
25
+
26
+ # Copyright (c) 2010 OpenLogic
27
+ #
28
+ # Licensed under MIT license. See LICENSE.txt
29
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 3.1.2
@@ -0,0 +1,70 @@
1
+ require 'logger'
2
+
3
+ module SamlSp
4
+
5
+ # :stopdoc:
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ VERSION = ::File.read(PATH + 'VERSION').strip
9
+ # :startdoc:
10
+
11
+ # Returns the version string for the library.
12
+ #
13
+ def self.version
14
+ VERSION
15
+ end
16
+
17
+ # Returns the library path for the module. If any arguments are given,
18
+ # they will be joined to the end of the libray path using
19
+ # <tt>File.join</tt>.
20
+ #
21
+ def self.libpath( *args )
22
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
23
+ end
24
+
25
+ # Returns the lpath for the module. If any arguments are given,
26
+ # they will be joined to the end of the path using
27
+ # <tt>File.join</tt>.
28
+ #
29
+ def self.path( *args )
30
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
31
+ end
32
+
33
+ # Logger that does nothing
34
+ BITBUCKET_LOGGER = Logger.new(nil)
35
+ class << BITBUCKET_LOGGER
36
+ def add(*args)
37
+ end
38
+ end
39
+
40
+ # The logger saml-sp should use
41
+ def self.logger
42
+ @@logger ||= BITBUCKET_LOGGER
43
+ end
44
+
45
+ # Set the logger for saml-sp
46
+ def self.logger=(a_logger)
47
+ @@logger = a_logger
48
+ end
49
+
50
+ module Logging
51
+ def logger
52
+ SamlSp.logger
53
+ end
54
+
55
+ def self.included(base)
56
+ base.extend(self)
57
+ end
58
+ end
59
+
60
+ autoload :Config, 'saml_sp/config'
61
+ end # module SamlSp
62
+
63
+ require 'rubygems'
64
+
65
+ autoload :Saml2, 'saml2'
66
+
67
+
68
+ # Copyright (c) 2010 OpenLogic
69
+ #
70
+ # Licensed under MIT license. See LICENSE.txt
@@ -0,0 +1,11 @@
1
+ module Saml2
2
+ autoload :Type4Artifact, 'saml2/type4_artifact'
3
+ autoload :Assertion, 'saml2/assertion'
4
+ autoload :ArtifactResolver, 'saml2/artifact_resolver'
5
+ end
6
+
7
+
8
+ # Copyright (c) 2010 OpenLogic
9
+ #
10
+ # Licensed under MIT license. See LICENSE.txt
11
+
@@ -0,0 +1,191 @@
1
+ require 'addressable/uri'
2
+ gem 'openlogic-resourceful'
3
+ require 'resourceful'
4
+ require 'nokogiri'
5
+ require 'uuidtools'
6
+
7
+ module Saml2
8
+ class NoSuchResolverError < StandardError
9
+ end
10
+
11
+ class RequestDeniedError < StandardError
12
+ end
13
+
14
+ class AnomalousResponseIssuerError < StandardError
15
+ def self.new_from_issuers(expected, actual)
16
+ new "Issuer should have been <#{expected}> but was <#{actual}>"
17
+ end
18
+ end
19
+
20
+ class ArtifactResolver
21
+ attr_reader :source_id, :resolution_service_uri, :idp_id, :sp_id
22
+ attr_reader :basic_auth_realm, :basic_auth_user_id, :basic_auth_password
23
+
24
+ # Initialize and register a new artifact resolver.
25
+ #
26
+ # @param [string] source_id An opacque identifier used by the IDP
27
+ # to identify artifact that can be resolved by this service.
28
+ #
29
+ # @param [string] resolution_service_uri The URI that will resolve
30
+ # artifacts into assertions.
31
+ #
32
+ # @param [String] idp_id The URI identifying the assertion issuer at this
33
+ # source.
34
+ #
35
+ # @param [String] sp_id The URI identifying (for this source) the service
36
+ # provider. IOW, the id of your application.
37
+ def initialize(source_id, resolution_service_uri, idp_id, sp_id)
38
+ @source_id = source_id
39
+ @resolution_service_uri = Addressable::URI.parse(resolution_service_uri)
40
+ @idp_id = idp_id
41
+ @sp_id = sp_id
42
+ ArtifactResolverRegistry.register self
43
+ end
44
+
45
+ # Set HTTP basic authentication credentials
46
+ def basic_auth_credentials(user_id, password, realm = nil)
47
+ @basic_auth_realm = realm
48
+ @basic_auth_user_id = user_id
49
+ @basic_auth_password = password
50
+ end
51
+
52
+ def logger
53
+ SamlSp.logger
54
+ end
55
+
56
+ def http
57
+ @http ||= Resourceful::HttpAccessor.new(:authenticators => authenticator, :logger => logger)
58
+ end
59
+
60
+ def authenticator
61
+ return nil unless basic_auth_user_id
62
+
63
+ if basic_auth_realm
64
+ Resourceful::BasicAuthenticator.new(basic_auth_realm, basic_auth_user_id, basic_auth_password)
65
+ else
66
+ Resourceful::PromiscuousBasicAuthenticator.new(basic_auth_user_id, basic_auth_password)
67
+ end
68
+ end
69
+
70
+ # Resolve `artifact` into an Assertion.
71
+ #
72
+ # @param [Saml2::Type4Artifact] The artifact to resolve.
73
+ #
74
+ # @return [Saml2::Assertion]
75
+ #
76
+ # @raise [RequestDeniedError] When the resolution service refuses
77
+ # to resolve the artifact.
78
+ #
79
+ # @raise [AnomalousResponseIssuerError] When the issuer of the
80
+ # response do not match the idp_id for this source.
81
+ def resolve(artifact)
82
+ soap_body = request_document_for(artifact)
83
+ logger.debug{"ArtifactResolve request body:\n#{soap_body.gsub(/^/, "\t")}"}
84
+ resp = http.resource(resolution_service_uri).post(soap_body,
85
+ 'Accept' => 'application/soap+xml',
86
+ 'Content-Type' => 'application/soap+xml')
87
+
88
+ doc = Nokogiri::XML.parse(resp.body)
89
+ assert_successful_response(doc)
90
+
91
+ assertion = Assertion.new_from_xml(doc)
92
+
93
+ raise AnomalousResponseIssuerError.new_from_issuers(idp_id, assertion.issuer) unless
94
+ assertion.issuer == idp_id
95
+
96
+ assertion
97
+
98
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
99
+
100
+ logger.debug {
101
+ body = e.http_request.body
102
+ body.rewind
103
+ "Artifact resolution request:\n" + body.read.gsub(/^/, ' ')}
104
+ logger.debug {"Artifact resolution response:\n" + e.http_response.body.gsub(/^/, ' ')}
105
+ raise
106
+ end
107
+
108
+ def to_s
109
+ "Resolver for <#{idp_id}> (#{Base64.encode64(source_id).strip})"
110
+ end
111
+
112
+ protected
113
+
114
+ def assert_successful_response(resp_doc)
115
+ response_code = resp_doc.at("//sp:StatusCode/@Value", namespaces).content.strip
116
+ return if response_code == 'urn:oasis:names:tc:SAML:2.0:status:Success'
117
+
118
+ # Request was not handled successfully
119
+ err_message = "Request failed"
120
+
121
+ status_message_elem = resp_doc.at("//sp:StatusMessage", namespaces)
122
+ if status_message_elem
123
+ err_message << " because \"#{status_message_elem.content.strip}\""
124
+ end
125
+
126
+ err_message << ". (status code: #{response_code})"
127
+
128
+ if status_details_elem = resp_doc.at("//sp:StatusDetail", namespaces)
129
+ logger.debug "Details for resolve artifact failure (status code: #{response_code}):\n" + status_details_elem.content
130
+ end
131
+
132
+ raise RequestDeniedError, err_message
133
+ end
134
+
135
+ def namespaces
136
+ {'sp' => 'urn:oasis:names:tc:SAML:2.0:protocol',
137
+ 'sa' => 'urn:oasis:names:tc:SAML:2.0:assertion'}
138
+ end
139
+ def request_document_for(artifact)
140
+ <<XML
141
+ <?xml version="1.0"?>
142
+ <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
143
+ <SOAP-ENV:Body>
144
+ <ArtifactResolve IssueInstant="2006-12-15T15:35:12.068Z"
145
+ Version="2.0"
146
+ ID="_#{UUIDTools::UUID.random_create}"
147
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
148
+ xmlns="urn:oasis:names:tc:SAML:2.0:protocol">
149
+ <saml:Issuer>#{sp_id}</saml:Issuer>
150
+ <Artifact>#{artifact.to_s}</Artifact>
151
+ </ArtifactResolve>
152
+ </SOAP-ENV:Body>
153
+ </SOAP-ENV:Envelope>
154
+ XML
155
+ end
156
+ end
157
+
158
+ # Returns an artifact resolver that can be used to resolve artifacts
159
+ # from the specified source.
160
+ #
161
+ # @param [String] source_id The id of the source of interest.
162
+ def self.ArtifactResolver(source_id)
163
+ ArtifactResolverRegistry.lookup_by_source_id(source_id)
164
+ end
165
+
166
+ ArtifactResolverRegistry = Class.new do
167
+ include SamlSp::Logging
168
+
169
+ def register(resolver)
170
+ resolvers_table[resolver.source_id] = resolver
171
+
172
+ logger.info "saml-sp: #{resolver}' registered"
173
+ end
174
+
175
+ def lookup_by_source_id(source_id)
176
+ resolvers_table[source_id] || raise(NoSuchResolverError, "No resolver registered for source `#{Base64.encode64(source_id).strip}`")
177
+ end
178
+
179
+ protected
180
+
181
+ def resolvers_table
182
+ @resolvers_table ||= {}
183
+ end
184
+ end.new
185
+
186
+ end
187
+
188
+ # Copyright (c) 2010 OpenLogic
189
+ #
190
+ # Licensed under MIT license. See LICENSE.txt
191
+