openlogic-saml-sp 3.1.3

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 openlogic-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 = 'openlogic-saml-sp'
5
+ gemspec.summary = 'SAML 2.0 SSO Sevice Provider Library'
6
+ gemspec.email = 'gbettridge@openlogic.com'
7
+ gemspec.authors = ["OpenLogic", "Peter Williams","Glen Aultman-Bettridge"]
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.3
@@ -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,192 @@
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 opaque 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.info{"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
+ logger.info{"ArtifactResolve response body:\n#{resp.gsub(/^/, "\t")}"}
89
+ doc = Nokogiri::XML.parse(resp.body)
90
+ assert_successful_response(doc)
91
+
92
+ assertion = Assertion.new_from_xml(doc)
93
+
94
+ raise AnomalousResponseIssuerError.new_from_issuers(idp_id, assertion.issuer) unless
95
+ assertion.issuer == idp_id
96
+
97
+ assertion
98
+
99
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
100
+
101
+ logger.debug {
102
+ body = e.http_request.body
103
+ body.rewind
104
+ "Artifact resolution request:\n" + body.read.gsub(/^/, ' ')}
105
+ logger.debug {"Artifact resolution response:\n" + e.http_response.body.gsub(/^/, ' ')}
106
+ raise
107
+ end
108
+
109
+ def to_s
110
+ "Resolver for <#{idp_id}> (#{Base64.encode64(source_id).strip})"
111
+ end
112
+
113
+ protected
114
+
115
+ def assert_successful_response(resp_doc)
116
+ response_code = resp_doc.at("//sp:StatusCode/@Value", namespaces).content.strip
117
+ return if response_code == 'urn:oasis:names:tc:SAML:2.0:status:Success'
118
+
119
+ # Request was not handled successfully
120
+ err_message = "Request failed"
121
+
122
+ status_message_elem = resp_doc.at("//sp:StatusMessage", namespaces)
123
+ if status_message_elem
124
+ err_message << " because \"#{status_message_elem.content.strip}\""
125
+ end
126
+
127
+ err_message << ". (status code: #{response_code})"
128
+
129
+ if status_details_elem = resp_doc.at("//sp:StatusDetail", namespaces)
130
+ logger.debug "Details for resolve artifact failure (status code: #{response_code}):\n" + status_details_elem.content
131
+ end
132
+
133
+ raise RequestDeniedError, err_message
134
+ end
135
+
136
+ def namespaces
137
+ {'sp' => 'urn:oasis:names:tc:SAML:2.0:protocol',
138
+ 'sa' => 'urn:oasis:names:tc:SAML:2.0:assertion'}
139
+ end
140
+ def request_document_for(artifact)
141
+ <<XML
142
+ <?xml version="1.0"?>
143
+ <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
144
+ <SOAP-ENV:Body>
145
+ <ArtifactResolve IssueInstant="2006-12-15T15:35:12.068Z"
146
+ Version="2.0"
147
+ ID="_#{UUIDTools::UUID.random_create}"
148
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
149
+ xmlns="urn:oasis:names:tc:SAML:2.0:protocol">
150
+ <saml:Issuer>#{sp_id}</saml:Issuer>
151
+ <Artifact>#{artifact.to_s}</Artifact>
152
+ </ArtifactResolve>
153
+ </SOAP-ENV:Body>
154
+ </SOAP-ENV:Envelope>
155
+ XML
156
+ end
157
+ end
158
+
159
+ # Returns an artifact resolver that can be used to resolve artifacts
160
+ # from the specified source.
161
+ #
162
+ # @param [String] source_id The id of the source of interest.
163
+ def self.ArtifactResolver(source_id)
164
+ ArtifactResolverRegistry.lookup_by_source_id(source_id)
165
+ end
166
+
167
+ ArtifactResolverRegistry = Class.new do
168
+ include SamlSp::Logging
169
+
170
+ def register(resolver)
171
+ resolvers_table[resolver.source_id] = resolver
172
+
173
+ logger.info "saml-sp: #{resolver}' registered"
174
+ end
175
+
176
+ def lookup_by_source_id(source_id)
177
+ resolvers_table[source_id] || raise(NoSuchResolverError, "No resolver registered for source `#{Base64.encode64(source_id).strip}`")
178
+ end
179
+
180
+ protected
181
+
182
+ def resolvers_table
183
+ @resolvers_table ||= {}
184
+ end
185
+ end.new
186
+
187
+ end
188
+
189
+ # Copyright (c) 2010 OpenLogic
190
+ #
191
+ # Licensed under MIT license. See LICENSE.txt
192
+