rack-saml 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.README.md.swp +0 -0
- data/README.md +45 -36
- data/config/{saml.yml → rack-saml.yml} +3 -2
- data/lib/rack/saml/metadata/abstract_metadata.rb +3 -3
- data/lib/rack/saml/metadata/onelogin_metadata.rb +2 -2
- data/lib/rack/saml/metadata/opensaml_metadata.rb +1 -1
- data/lib/rack/saml/metadata_handler.rb +3 -3
- data/lib/rack/saml/misc/onelogin_setting.rb +2 -2
- data/lib/rack/saml/request/abstract_request.rb +3 -3
- data/lib/rack/saml/request/onelogin_request.rb +2 -2
- data/lib/rack/saml/request/opensaml_request.rb +1 -1
- data/lib/rack/saml/request_handler.rb +3 -3
- data/lib/rack/saml/response/abstract_response.rb +3 -3
- data/lib/rack/saml/response/onelogin_response.rb +2 -2
- data/lib/rack/saml/response/opensaml_response.rb +1 -1
- data/lib/rack/saml/response_handler.rb +23 -9
- data/lib/rack/saml.rb +150 -32
- data/lib/rack-saml/version.rb +1 -1
- metadata +6 -5
data/.README.md.swp
ADDED
Binary file
|
data/README.md
CHANGED
@@ -1,23 +1,21 @@
|
|
1
1
|
# SAML (Shibboleth SP) middleware for Rack
|
2
2
|
|
3
|
-
This project is deeply inspired by rack-shibboleth and ruby-saml. It is recommended to use the defact SAML implementation such as OpenSAML from the security or the functional aspect. However, there are also requirements to use SAML for light weight applications implemented by Ruby. rack-shibboleth may be a candidate to support such kind of objective. However it lacks the configurability to fit OmniAuth and the upgrade path to secure and stable
|
3
|
+
This project is deeply inspired by rack-shibboleth and ruby-saml. It is recommended to use the defact SAML implementation such as OpenSAML from the security or the functional aspect. However, there are also requirements to use SAML for light weight applications implemented by Ruby. rack-shibboleth may be a candidate to support such kind of objective. However it lacks the configurability to fit OmniAuth and OmniAuth Shibboleth Strategy. It also lacks the upgrade path to the secure and the stable SAML implementation like OpenSAML. So thus I just implemented a prototype to support SAML (Shibboleth SP) for Rack middleware.
|
4
4
|
|
5
5
|
OmniAuth Shibboleth Strategy
|
6
6
|
https://github.com/toyokazu/omniauth-shibboleth
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
### Signing AuthnRequest
|
8
|
+
rack-saml uses external libraries to generate and validate SAML AuthnRequest/Response. It uses Rack functions to implement SAML Transport (HTTP Redirect Binding and HTTP POST Binding).
|
11
9
|
|
12
|
-
|
10
|
+
## Changes
|
13
11
|
|
14
|
-
|
12
|
+
* version 0.0.2: SP session is supported using Rack::Session for Rack applications and ActionDispatch::Session for Rails applications.
|
15
13
|
|
16
|
-
|
14
|
+
## Limitations
|
17
15
|
|
18
|
-
###
|
16
|
+
### AuthnRequest Signing and Response Encryption
|
19
17
|
|
20
|
-
Current implementation does not support
|
18
|
+
Current implementation supports only Onelogin SAML assertion handler. It does not support to sign AuthnRequest and encrypt Response. So thus, the assertion encription function should be disabled at IdP side for rack-saml SPs.
|
21
19
|
|
22
20
|
## Getting Started
|
23
21
|
|
@@ -33,15 +31,17 @@ Current implementation does not support keeping session at SP side.
|
|
33
31
|
|
34
32
|
### Setup Rack::Saml middleware
|
35
33
|
|
34
|
+
Rack::Saml uses Rack::Session functions. You have to insert Rack::Session before Rack::Saml middleware. Rack::Session::Cookie is used in the following examples because it is easiest to setup and scale. You can use the other Rack::Session implementation. In a Rails application, it uses ActionDispatch::Session which is compatible with Rack::Session by default. So thus, you do not need to add Rack::Session in the Rails application.
|
35
|
+
|
36
36
|
#### For Rack applicaitons
|
37
37
|
|
38
38
|
In the following example, config.ru is used to add Rack::Saml middleware into a Rails application.
|
39
39
|
|
40
40
|
% vi config.ru
|
41
|
-
use Rack::
|
41
|
+
use Rack::Session::Cookie, :secret => 'pass_to_auth_session'
|
42
|
+
use Rack::Saml, {:config => "#{Rails.root}/config/rack-saml.yml",
|
42
43
|
:metadata => "#{Rails.root}/config/metadata.yml",
|
43
|
-
:attribute_map => "#{Rails.root}/config/attribute-map.yml"
|
44
|
-
:shib_app_id => "default"}
|
44
|
+
:attribute_map => "#{Rails.root}/config/attribute-map.yml"}
|
45
45
|
|
46
46
|
#### For Ralis applications
|
47
47
|
|
@@ -50,52 +50,58 @@ In the following example, config/application.rb is used to Rack::Saml middleware
|
|
50
50
|
% vi config/application.rb
|
51
51
|
module TestRackSaml
|
52
52
|
class Application < Rails::Application
|
53
|
-
|
53
|
+
config.middleware.use Rack::Saml, {:config => "#{Rails.root}/config/rack-saml.yml",
|
54
54
|
:metadata => "#{Rails.root}/config/metadata.yml",
|
55
|
-
:attribute_map => "#{Rails.root}/config/attribute-map.yml"
|
56
|
-
:shib_app_id => "default"}
|
55
|
+
:attribute_map => "#{Rails.root}/config/attribute-map.yml"}
|
57
56
|
...
|
58
57
|
|
58
|
+
#### Middleware options
|
59
|
+
|
60
|
+
* *:config* path to rack-saml.yml file
|
61
|
+
* *:metadata* path to metadata.yml file
|
62
|
+
* *:attribute_map* path to attribute-map.yml file
|
63
|
+
|
59
64
|
If you just want to test Rack::Saml, you can ommit middleware options in the both example (config.ru or config/application.rb).
|
60
65
|
|
61
66
|
use Rack::Saml
|
62
67
|
|
63
|
-
|
68
|
+
It may be useful for a tutorial use. At least, saml_idp or shib_ds in rack-saml.yml and metadata.yml must be configured to fit your environment.
|
64
69
|
|
65
70
|
Rack::Saml uses default configurations located in the rack-saml gem path.
|
66
71
|
|
67
72
|
$GEM_HOME/rack-saml-x.x.x/config/xxx.yml
|
68
73
|
|
69
|
-
|
70
|
-
|
71
|
-
* *:saml_config* path to saml.yml file
|
72
|
-
* *:metadata* path to metadata.yml file
|
73
|
-
* *:attribute_map* path to attribute-map.yml file
|
74
|
-
* *:shib_app_id* If you want to use the middleware as Shibboleth SP, you should specify an application ID. In the Shibboleth SP default configuration, 'default' is used as the application ID.
|
75
|
-
|
74
|
+
Please copy them to an arbitrary directory and edit them if you need. If you want to use your customized configuration file, do not forget to specify the configuration file path by middleware options.
|
76
75
|
|
77
76
|
#### Configuration files
|
78
77
|
|
79
78
|
You can find default configuration files at
|
80
79
|
|
81
|
-
$GEM_HOME/rack-saml-x.x.x/config/
|
80
|
+
$GEM_HOME/rack-saml-x.x.x/config/xxx.yml
|
82
81
|
|
83
|
-
##### saml.yml
|
82
|
+
##### rack-saml.yml
|
84
83
|
|
85
|
-
Configuration to set SAML parameters.
|
84
|
+
Configuration to set SAML parameters. At least, you must configure saml_idp or shib_ds. They depends on your environments.
|
86
85
|
|
87
|
-
* *protected_path*
|
88
|
-
* *protected_path_regexp* use regular expression to match protected_path or not e.g. true / false
|
86
|
+
* *protected_path* path name where rack-saml protects, e.g. /auth/shibboleth/callback (default path for OmniAuth Shibboleth Strategy)
|
89
87
|
* *metadata_path* the path name where SP's metadata is generated
|
90
88
|
* *assertion_handler* 'onelogin' / 'opensaml' (not implemented yet)
|
91
|
-
* *saml_idp*
|
92
|
-
* *
|
93
|
-
* *
|
94
|
-
* *
|
89
|
+
* *saml_idp* IdP's entity ID which is used to authenticate user. This parameter can be omitted when you use Shibboleth Discovery Service (shib_ds).
|
90
|
+
* *saml_sess_timeout* SP session timeout (default: 1800 seconds)
|
91
|
+
* *shib_app_id* If you want to use the middleware as Shibboleth SP, you should specify an application ID. In the Shibboleth SP default configuration, 'default' is used as the application ID.
|
92
|
+
* *shib_ds* If you want to use the middleware as Shibboleth SP and use discovery service, specify the uri of the Discovery Service.
|
93
|
+
* *sp_cert* path to the SAML SP's certificate file, e.g. cert.pem (AuthnRequest Signing and Response Encryption are not supported yet)
|
94
|
+
* *sp_key* path to the SAML SP's key file, e.g. key.pem (AuthnRequest Signing and Response Encryption are not supported yet)
|
95
|
+
|
96
|
+
SAML SP's entity ID (saml_sp) is automatically generated from request URI and /rack-saml-sp (fixed path name). The Assertion Consumer Service URI is generated from request URI and protected_path.
|
97
|
+
|
98
|
+
saml_sp_prefix = "#{request.scheme}://#{request.host}#{":#{request.port}" if request.port}#{request.script_name}"
|
99
|
+
@config['saml_sp'] = "#{saml_sp_prefix}/rack-saml-sp"
|
100
|
+
@config['assertion_consumer_service_uri'] = "#{saml_sp_prefix}#{@config['protected_path']}"
|
95
101
|
|
96
102
|
##### metadata.yml
|
97
103
|
|
98
|
-
To connect to an IdP, you must describe IdP's specification. In rack-saml, it should be written in metadata.yml. metadata.yml file include the following lists.
|
104
|
+
To connect to an IdP, you must describe IdP's specification. In rack-saml, it should be written in metadata.yml. metadata.yml file include the following lists. You must generate your own metadata.yml by using conv_metadata.rb.
|
99
105
|
|
100
106
|
* *idp_lists* list of IdP metadata
|
101
107
|
* *sp_lists* list of SP metadata
|
@@ -112,7 +118,7 @@ parameters of the sp_lists (currently not used):
|
|
112
118
|
* *certificate* base64 encoded certificate of SP
|
113
119
|
* *saml2_http_post* Location attribute of the SP's assertion consumer uri with HTTP POST Binding
|
114
120
|
|
115
|
-
These parameters are automatically extracted from SAML metadata. You can use conv_metadata.rb command for extraction.
|
121
|
+
These parameters are automatically extracted from SAML metadata (XML). You can use conv_metadata.rb command for extraction.
|
116
122
|
|
117
123
|
% $GEM_HOME/rack-saml-x.x.x/bin/conv_metadata.rb metadata.xml > metadata.yml
|
118
124
|
|
@@ -124,6 +130,8 @@ attribute-map.yml can extract attributes from SAML Response and put attributes o
|
|
124
130
|
"urn:oid:0.9.2342.19200300.100.1.1": "uid"
|
125
131
|
...
|
126
132
|
|
133
|
+
You can use default attribute-map.yml file. If you want to add new attributes, please refer the attribute-map.xml file used in Shibboleth SP.
|
134
|
+
|
127
135
|
### Setup IdP to accept rack-saml SP
|
128
136
|
|
129
137
|
#### SP Metadata generation
|
@@ -132,7 +140,7 @@ To connect a new SP to the existing IdP, you need to import SP's metadata into t
|
|
132
140
|
|
133
141
|
#### IdP configuration examples not to encrypt assertion
|
134
142
|
|
135
|
-
Current rack-saml implementation does not support assertion encryption because Onelogin::Saml does not support
|
143
|
+
Current rack-saml implementation does not support assertion encryption because Onelogin::Saml does not support AuthnRequest signing and Response encryption. So thus, in the followings, we would like to show sample configurations to disable encryption in IdP assertion processing. These are not recommended for sensitive applications.
|
136
144
|
|
137
145
|
##### Shibboleth IdP example
|
138
146
|
|
@@ -140,12 +148,13 @@ Add the following configuration after <rp:DefaultRelyingParty> in relying-party.
|
|
140
148
|
|
141
149
|
% vi $IDP_HOME/conf/relying-party.xml
|
142
150
|
...
|
143
|
-
<rp:RelyingParty id="http://example.com:3000/
|
151
|
+
<rp:RelyingParty id="http://example.com:3000/rack-saml-sp" provider="http://idp.example.com/idp/shibboleth" defaultSigningCredentialRef="IdPCredential">
|
144
152
|
<rp:ProfileConfiguration xsi:type="saml:SAML2SSOProfile" includeAttributeStatement="true" assertionLifetime="PT5M" assertionProxyCount="0" signResponses="never" signAssertions="always" encryptAssertions="never" encryptNameIds="never"/>
|
145
153
|
</rp:RelyingParty>
|
146
154
|
|
147
155
|
## TODO
|
148
156
|
|
157
|
+
* write spec files
|
149
158
|
* ruby-opensaml (I hope someone implement it :)
|
150
159
|
|
151
160
|
## License
|
@@ -1,6 +1,7 @@
|
|
1
1
|
protected_path: /auth/shibboleth/callback
|
2
|
-
# protected_path_regexp: true
|
3
2
|
metadata_path: /Shibboleth.sso/Metadata
|
4
3
|
assertion_handler: onelogin
|
5
4
|
saml_idp: https://localhost/idp/shibboleth
|
6
|
-
|
5
|
+
saml_sess_timeout: 1800
|
6
|
+
shib_app_id: default
|
7
|
+
shibb_ds: https://localhost/discovery/WAYF
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Rack
|
2
2
|
class Saml
|
3
3
|
class AbstractMetadata
|
4
|
-
attr_reader :request, :
|
4
|
+
attr_reader :request, :config, :metadata
|
5
5
|
|
6
|
-
def initialize(request,
|
6
|
+
def initialize(request, config, metadata)
|
7
7
|
@request = request
|
8
|
-
@
|
8
|
+
@config = config
|
9
9
|
@metadata = metadata
|
10
10
|
end
|
11
11
|
|
@@ -5,8 +5,8 @@ module Rack
|
|
5
5
|
class OneloginMetadata < AbstractMetadata
|
6
6
|
include OneloginSetting
|
7
7
|
|
8
|
-
def initialize(request,
|
9
|
-
super(request,
|
8
|
+
def initialize(request, config, metadata)
|
9
|
+
super(request, config, metadata)
|
10
10
|
@sp_metadata = Onelogin::Saml::Metadata.new
|
11
11
|
end
|
12
12
|
|
@@ -9,10 +9,10 @@ module Rack
|
|
9
9
|
|
10
10
|
# Rack::Saml::MetadataHandler
|
11
11
|
# request: Rack current request instance
|
12
|
-
#
|
12
|
+
# config: config/rack-saml.yml
|
13
13
|
# metadata: specified idp entity in the config/metadata.yml
|
14
|
-
def initialize(request,
|
15
|
-
@sp_metadata = (eval "Rack::Saml::#{
|
14
|
+
def initialize(request, config, metadata)
|
15
|
+
@sp_metadata = (eval "Rack::Saml::#{config['assertion_handler'].to_s.capitalize}Metadata").new(request, config, metadata)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -5,8 +5,8 @@ module Rack
|
|
5
5
|
|
6
6
|
def saml_settings
|
7
7
|
settings = Onelogin::Saml::Settings.new
|
8
|
-
settings.assertion_consumer_service_url =
|
9
|
-
settings.issuer = @
|
8
|
+
settings.assertion_consumer_service_url = @config['assertion_consumer_service_uri']
|
9
|
+
settings.issuer = @config['saml_sp']
|
10
10
|
settings.idp_sso_target_url = @metadata['saml2_http_redirect']
|
11
11
|
settings.idp_cert = @metadata['certificate']
|
12
12
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Rack
|
2
2
|
class Saml
|
3
3
|
class AbstractRequest
|
4
|
-
attr_reader :request, :
|
4
|
+
attr_reader :request, :config, :metadata
|
5
5
|
|
6
|
-
def initialize(request,
|
6
|
+
def initialize(request, config, metadata)
|
7
7
|
@request = request
|
8
|
-
@
|
8
|
+
@config = config
|
9
9
|
@metadata = metadata
|
10
10
|
end
|
11
11
|
|
@@ -5,8 +5,8 @@ module Rack
|
|
5
5
|
class OneloginRequest < AbstractRequest
|
6
6
|
include OneloginSetting
|
7
7
|
|
8
|
-
def initialize(request,
|
9
|
-
super(request,
|
8
|
+
def initialize(request, config, metadata)
|
9
|
+
super(request, config, metadata)
|
10
10
|
@authrequest = Onelogin::Saml::Authrequest.new
|
11
11
|
end
|
12
12
|
|
@@ -9,10 +9,10 @@ module Rack
|
|
9
9
|
|
10
10
|
# Rack::Saml::RequestHandler
|
11
11
|
# request: Rack current request instance
|
12
|
-
#
|
12
|
+
# config: config/saml.yml
|
13
13
|
# metadata: specified idp entity in the config/metadata.yml
|
14
|
-
def initialize(request,
|
15
|
-
@authn_request = (eval "Rack::Saml::#{
|
14
|
+
def initialize(request, config, metadata)
|
15
|
+
@authn_request = (eval "Rack::Saml::#{config['assertion_handler'].to_s.capitalize}Request").new(request, config, metadata)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Rack
|
2
2
|
class Saml
|
3
3
|
class AbstractResponse
|
4
|
-
attr_reader :request, :
|
4
|
+
attr_reader :request, :config, :metadata
|
5
5
|
|
6
|
-
def initialize(request,
|
6
|
+
def initialize(request, config, metadata)
|
7
7
|
@request = request
|
8
|
-
@
|
8
|
+
@config = config
|
9
9
|
@metadata = metadata
|
10
10
|
end
|
11
11
|
|
@@ -6,8 +6,8 @@ module Rack
|
|
6
6
|
include OneloginSetting
|
7
7
|
#extend Forwardable
|
8
8
|
|
9
|
-
def initialize(request,
|
10
|
-
super(request,
|
9
|
+
def initialize(request, config, metadata)
|
10
|
+
super(request, config, metadata)
|
11
11
|
@response = Onelogin::Saml::Response.new(@request.params['SAMLResponse'])
|
12
12
|
@response.settings = saml_settings
|
13
13
|
end
|
@@ -9,19 +9,33 @@ module Rack
|
|
9
9
|
|
10
10
|
# Rack::Saml::ResponseHandler
|
11
11
|
# request: Rack current request instance
|
12
|
-
#
|
12
|
+
# config: config/saml.yml
|
13
13
|
# metadata: specified idp entity in the config/metadata.yml
|
14
|
-
def initialize(request,
|
15
|
-
@response = (eval "Rack::Saml::#{
|
14
|
+
def initialize(request, config, metadata)
|
15
|
+
@response = (eval "Rack::Saml::#{config['assertion_handler'].to_s.capitalize}Response").new(request, config, metadata)
|
16
16
|
end
|
17
17
|
|
18
|
-
def extract_attrs(env,
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def extract_attrs(env, session, attribute_map)
|
19
|
+
if session.env.empty?
|
20
|
+
attribute_map.each do |attr_name, env_name|
|
21
|
+
attribute = @response.attributes[attr_name]
|
22
|
+
if !attribute.nil?
|
23
|
+
session.env[env_name] = attribute
|
24
|
+
end
|
25
|
+
end
|
26
|
+
if !@response.config['shib_app_id'].nil?
|
27
|
+
session.env['Shib-Application-ID'] = @response.config['shib_app_id']
|
28
|
+
session.env['Shib-Session-ID'] = session.get_sid('saml_res')
|
29
|
+
end
|
22
30
|
end
|
23
|
-
|
24
|
-
env[
|
31
|
+
session.env.each do |k, v|
|
32
|
+
env[k] = v
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.extract_attrs(env, session)
|
37
|
+
session.env.each do |k, v|
|
38
|
+
env[k] = v
|
25
39
|
end
|
26
40
|
end
|
27
41
|
end
|
data/lib/rack/saml.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'yaml'
|
3
|
+
require 'securerandom'
|
3
4
|
|
4
5
|
module Rack
|
5
6
|
# Rack::Saml
|
@@ -10,20 +11,37 @@ module Rack
|
|
10
11
|
# To establish single path behavior, it currently supports only
|
11
12
|
# HTTP Redirect Binding from SP to Idp
|
12
13
|
# HTTP POST Binding from IdP to SP
|
14
|
+
#
|
15
|
+
# rack-saml uses rack.session to store SAML and Discovery Service
|
16
|
+
# status.
|
17
|
+
# env['rack.session'] = {
|
18
|
+
# 'rack_saml' => {
|
19
|
+
# 'ds.session' => {
|
20
|
+
# 'sid' => temporally_generated_hash,
|
21
|
+
# 'expire_at' => xxxxx # timestamp
|
22
|
+
# }
|
23
|
+
# 'saml_authreq.session' => {
|
24
|
+
# 'sid' => temporally_generated_hash,
|
25
|
+
# 'expire_at' => xxxxx # timestamp
|
26
|
+
# }
|
27
|
+
# 'saml_res.session' => {
|
28
|
+
# 'sid' => temporally_generated_hash,
|
29
|
+
# 'expire_at' => xxxxx # timestamp,
|
30
|
+
# 'env' => {}
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
# }
|
13
34
|
class Saml
|
14
35
|
autoload "RequestHandler", 'rack/saml/request_handler'
|
15
36
|
autoload "MetadataHandler", 'rack/saml/metadata_handler'
|
16
37
|
autoload "ResponseHandler", 'rack/saml/response_handler'
|
17
38
|
|
18
|
-
class SamlAssertionError < StandardError
|
19
|
-
end
|
20
|
-
|
21
39
|
def default_config_path(config_file)
|
22
40
|
::File.expand_path("../../../config/#{config_file}", __FILE__)
|
23
41
|
end
|
24
42
|
|
25
|
-
def
|
26
|
-
default_config_path('saml.yml')
|
43
|
+
def default_config
|
44
|
+
default_config_path('rack-saml.yml')
|
27
45
|
end
|
28
46
|
|
29
47
|
def default_metadata
|
@@ -38,12 +56,12 @@ module Rack
|
|
38
56
|
@app = app
|
39
57
|
@opts = opts
|
40
58
|
|
41
|
-
if @opts[:
|
42
|
-
@opts[:
|
59
|
+
if @opts[:config].nil? || !::File.exists?(@opts[:config])
|
60
|
+
@opts[:config] = default_config
|
43
61
|
end
|
44
|
-
@
|
45
|
-
if @
|
46
|
-
raise ArgumentError, "'assertion_handler' parameter should be specified in the :
|
62
|
+
@config = YAML.load_file(@opts[:config])
|
63
|
+
if @config['assertion_handler'].nil?
|
64
|
+
raise ArgumentError, "'assertion_handler' parameter should be specified in the :config file"
|
47
65
|
end
|
48
66
|
if @opts[:metadata].nil? || !::File.exists?(@opts[:metadata])
|
49
67
|
@opts[:metadata] = default_metadata
|
@@ -55,8 +73,82 @@ module Rack
|
|
55
73
|
@attribute_map = YAML.load_file(@opts[:attribute_map])
|
56
74
|
end
|
57
75
|
|
76
|
+
class Session
|
77
|
+
RACK_SAML_COOKIE = '_rack_saml'
|
78
|
+
def initialize(env)
|
79
|
+
@rack_session = env['rack.session']
|
80
|
+
if @rack_session[RACK_SAML_COOKIE].nil?
|
81
|
+
@session = @rack_session[RACK_SAML_COOKIE] = {
|
82
|
+
'ds.session' => {},
|
83
|
+
'saml_authreq.session' => {},
|
84
|
+
'saml_res.session' => {'env' => {}}
|
85
|
+
}
|
86
|
+
else
|
87
|
+
@session = @rack_session[RACK_SAML_COOKIE]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def generate_sid(length = 32)
|
92
|
+
SecureRandom.hex(length)
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_sid(type)
|
96
|
+
@session["#{type}.session"]['sid']
|
97
|
+
end
|
98
|
+
|
99
|
+
def start(type, timeout = 300)
|
100
|
+
sid = nil
|
101
|
+
if timeout.nil?
|
102
|
+
period = nil
|
103
|
+
else
|
104
|
+
period = Time.now + timeout
|
105
|
+
end
|
106
|
+
case type
|
107
|
+
when 'ds'
|
108
|
+
sid = generate_sid(4)
|
109
|
+
when 'saml_authreq'
|
110
|
+
sid = generate_sid
|
111
|
+
when 'saml_res'
|
112
|
+
sid = generate_sid
|
113
|
+
end
|
114
|
+
@session["#{type}.session"]['sid'] = sid
|
115
|
+
@session["#{type}.session"]['expired_at'] = period
|
116
|
+
@session["#{type}.session"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def finish(type)
|
120
|
+
@session["#{type}.session"] = {}
|
121
|
+
end
|
122
|
+
|
123
|
+
def env
|
124
|
+
@session['saml_res.session']['env']
|
125
|
+
end
|
126
|
+
|
127
|
+
def is_valid?(type, sid = nil)
|
128
|
+
session = @session["#{type}.session"]
|
129
|
+
return false if session['sid'].nil? # no valid session
|
130
|
+
if session['expired_at'].nil? # no expiration
|
131
|
+
return true if sid.nil? # no sid check
|
132
|
+
return true if session['sid'] == sid # sid check
|
133
|
+
else
|
134
|
+
if Time.now < Time.at(session['expired_at']) # before expiration
|
135
|
+
return true if sid.nil? # no sid check
|
136
|
+
return true if session['sid'] == sid # sid check
|
137
|
+
end
|
138
|
+
end
|
139
|
+
false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
58
143
|
def call env
|
144
|
+
session = Session.new(env)
|
59
145
|
request = Rack::Request.new env
|
146
|
+
# saml_sp: SAML SP's entity_id
|
147
|
+
# generate saml_sp from request uri and default path (rack-saml-sp)
|
148
|
+
saml_sp_prefix = "#{request.scheme}://#{request.host}#{":#{request.port}" if request.port}#{request.script_name}"
|
149
|
+
@config['saml_sp'] = "#{saml_sp_prefix}/rack-saml-sp"
|
150
|
+
@config['assertion_consumer_service_uri'] = "#{saml_sp_prefix}#{@config['protected_path']}"
|
151
|
+
# for debug
|
60
152
|
#return [
|
61
153
|
# 403,
|
62
154
|
# {
|
@@ -67,26 +159,46 @@ module Rack
|
|
67
159
|
#]
|
68
160
|
if request.request_method == 'GET'
|
69
161
|
if match_protected_path?(request) # generate AuthnRequest
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
162
|
+
if session.is_valid?('saml_res') # the client already has a valid session
|
163
|
+
ResponseHandler.extract_attrs(request, session)
|
164
|
+
else
|
165
|
+
if !@config['shib_ds'].nil? # use discovery service (ds)
|
166
|
+
if request.params['entityID'].nil? # start ds session
|
167
|
+
session.start('ds')
|
168
|
+
return Rack::Response.new.tap { |r|
|
169
|
+
r.redirect "#{@config['shib_ds']}?entityID=#{URI.encode(@config['saml_sp'], /[^\w]/)}&return=#{URI.encode("#{@config['assertion_consumer_service_uri']}?target=#{session.get_sid('ds')}", /[^\w]/)}"
|
170
|
+
}.finish
|
171
|
+
end
|
172
|
+
if !session.is_valid?('ds', request.params['target']) # confirm ds session
|
173
|
+
current_sid = session.get_sid('ds')
|
174
|
+
session.finish('ds')
|
175
|
+
return create_response(500, 'text/html', "Internal Server Error: Invalid discovery service session current sid=#{current_sid}, request sid=#{request.params['target']}")
|
176
|
+
end
|
177
|
+
session.finish('ds')
|
178
|
+
@config['saml_idp'] = request.params['entityID']
|
179
|
+
end
|
180
|
+
session.start('saml_authreq')
|
181
|
+
handler = RequestHandler.new(request, @config, @metadata['idp_lists'][@config['saml_idp']])
|
182
|
+
return Rack::Response.new.tap { |r|
|
183
|
+
r.redirect handler.authn_request.redirect_uri
|
184
|
+
}.finish
|
185
|
+
end
|
74
186
|
elsif match_metadata_path?(request) # generate Metadata
|
75
|
-
handler = MetadataHandler.new(request, @
|
76
|
-
return
|
77
|
-
200,
|
78
|
-
{
|
79
|
-
'Content-Type' => 'application/samlmetadata+xml'
|
80
|
-
},
|
81
|
-
[handler.sp_metadata.generate]
|
82
|
-
]
|
187
|
+
handler = MetadataHandler.new(request, @config, @metadata['idp_lists'][@config['saml_idp']])
|
188
|
+
return create_response(200, 'application/samlmetadata+xml', handler.sp_metadata.generate)
|
83
189
|
end
|
84
190
|
elsif request.request_method == 'POST' && match_protected_path?(request) # process Response
|
85
|
-
|
86
|
-
|
87
|
-
handler.
|
191
|
+
if session.is_valid?('saml_authreq')
|
192
|
+
handler = ResponseHandler.new(request, @config, @metadata['idp_lists'][@config['saml_idp']])
|
193
|
+
if handler.response.is_valid?
|
194
|
+
session.finish('saml_authreq')
|
195
|
+
session.start('saml_res', @config['saml_sess_timeout'] || 1800)
|
196
|
+
handler.extract_attrs(env, session, @attribute_map)
|
197
|
+
else
|
198
|
+
return create_response(403, 'text/html', 'SAML Error: Invalid SAML response.')
|
199
|
+
end
|
88
200
|
else
|
89
|
-
|
201
|
+
return create_response(500, 'text/html', 'No valid AuthnRequest session.')
|
90
202
|
end
|
91
203
|
end
|
92
204
|
|
@@ -94,15 +206,21 @@ module Rack
|
|
94
206
|
end
|
95
207
|
|
96
208
|
def match_protected_path?(request)
|
97
|
-
|
98
|
-
# to be fixed (Regexp)
|
99
|
-
return (request.path_info =~ Regexp.new(@saml_config['protected_path']))
|
100
|
-
end
|
101
|
-
request.path_info == @saml_config['protected_path']
|
209
|
+
request.path_info == @config['protected_path']
|
102
210
|
end
|
103
211
|
|
104
212
|
def match_metadata_path?(request)
|
105
|
-
request.path_info == @
|
213
|
+
request.path_info == @config['metadata_path']
|
214
|
+
end
|
215
|
+
|
216
|
+
def create_response(code, content_type, message)
|
217
|
+
return [
|
218
|
+
code,
|
219
|
+
{
|
220
|
+
'Content-Type' => content_type
|
221
|
+
},
|
222
|
+
[message]
|
223
|
+
]
|
106
224
|
end
|
107
225
|
end
|
108
226
|
end
|
data/lib/rack-saml/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-01-06 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-saml
|
16
|
-
requirement: &
|
16
|
+
requirement: &2154954560 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: 0.4.7
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2154954560
|
25
25
|
description: SAML middleware for Rack (using ruby-saml)
|
26
26
|
email:
|
27
27
|
- toyokazu@gmail.com
|
@@ -29,11 +29,12 @@ executables: []
|
|
29
29
|
extensions: []
|
30
30
|
extra_rdoc_files: []
|
31
31
|
files:
|
32
|
+
- .README.md.swp
|
32
33
|
- bin/conv_metadata.rb
|
33
34
|
- config/attribute-map.yml
|
34
35
|
- config/attribute-map.yml.sample
|
35
36
|
- config/metadata.yml
|
36
|
-
- config/saml.yml
|
37
|
+
- config/rack-saml.yml
|
37
38
|
- Gemfile
|
38
39
|
- lib/rack/saml/metadata/abstract_metadata.rb
|
39
40
|
- lib/rack/saml/metadata/onelogin_metadata.rb
|