saml-sp 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+