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 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 middleware as OpenSAML. So thus I just implemented a prototype to support SAML (Shibboleth SP) for Rack middleware.
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
- ## Limitations
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
- Current implementation supports only Onelogin SAML assertion handler. It does not support to sign AuthnRequest.
10
+ ## Changes
13
11
 
14
- ### Encrypted Assertion
12
+ * version 0.0.2: SP session is supported using Rack::Session for Rack applications and ActionDispatch::Session for Rails applications.
15
13
 
16
- Current implementation does not support assertion encryption. So thus, assertion encription function should be disabled for rack-saml SPs.
14
+ ## Limitations
17
15
 
18
- ### SP Session
16
+ ### AuthnRequest Signing and Response Encryption
19
17
 
20
- Current implementation does not support keeping session at SP side.
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::Saml, {:saml_config => "#{Rails.root}/config/saml.yml",
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
- config.middleware.use Rack::Saml, {:saml_config => "#{Rails.root}/config/saml.yml",
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
- However, you can not omit :shib_app_id option if you want to use this middleware with OmniAuth::Shibboleth Strategy.
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
- #### Middleware options
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* string or regular expression of the path name where SAML protects, e.g. /auth/shibboleth/callback or '^\/secure\/[^\s]*'
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* idp_entity_id
92
- * *saml_sp* sp_entity_id (self id)
93
- * *sp_cert* path to the SAML SP's certificate file, e.g. cert.pem (signing AuthnRequest is not supported yet)
94
- * *sp_key* path to the SAML SP's key file, e.g. key.pem (signing AuthnRequest is not supported yet)
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 signature and encryption of assertion. 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.
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/auth/shibboleth/callback" provider="http://example.com:3000/auth/shibboleth/callback" defaultSigningCredentialRef="IdPCredential">
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
- saml_sp: http://localhost:3000/auth/shibboleth/callback
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, :saml_config, :metadata
4
+ attr_reader :request, :config, :metadata
5
5
 
6
- def initialize(request, saml_config, metadata)
6
+ def initialize(request, config, metadata)
7
7
  @request = request
8
- @saml_config = saml_config
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, saml_config, metadata)
9
- super(request, saml_config, metadata)
8
+ def initialize(request, config, metadata)
9
+ super(request, config, metadata)
10
10
  @sp_metadata = Onelogin::Saml::Metadata.new
11
11
  end
12
12
 
@@ -2,7 +2,7 @@ module Rack
2
2
  class Saml
3
3
  class OpensamlMetadata < AbstractMetadata
4
4
  # to be implemented
5
- def initialize(request, saml_config, metadata)
5
+ def initialize(request, config, metadata)
6
6
  end
7
7
 
8
8
  def generate
@@ -9,10 +9,10 @@ module Rack
9
9
 
10
10
  # Rack::Saml::MetadataHandler
11
11
  # request: Rack current request instance
12
- # saml_config: config/saml.yml
12
+ # config: config/rack-saml.yml
13
13
  # metadata: specified idp entity in the config/metadata.yml
14
- def initialize(request, saml_config, metadata)
15
- @sp_metadata = (eval "Rack::Saml::#{saml_config['assertion_handler'].to_s.capitalize}Metadata").new(request, saml_config, metadata)
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 = "#{@request.scheme}://#{@request.host}#{":#{@request.port}" if @request.port}#{request.script_name}#{@saml_config['protected_path']}"
9
- settings.issuer = @saml_config['saml_sp']
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, :saml_config, :metadata
4
+ attr_reader :request, :config, :metadata
5
5
 
6
- def initialize(request, saml_config, metadata)
6
+ def initialize(request, config, metadata)
7
7
  @request = request
8
- @saml_config = saml_config
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, saml_config, metadata)
9
- super(request, saml_config, metadata)
8
+ def initialize(request, config, metadata)
9
+ super(request, config, metadata)
10
10
  @authrequest = Onelogin::Saml::Authrequest.new
11
11
  end
12
12
 
@@ -2,7 +2,7 @@ module Rack
2
2
  class Saml
3
3
  class OpensamlRequest < AbstractRequest
4
4
  # To be implemented
5
- def initialize(request, saml_config, metadata)
5
+ def initialize(request, config, metadata)
6
6
  end
7
7
 
8
8
  def redirect_uri
@@ -9,10 +9,10 @@ module Rack
9
9
 
10
10
  # Rack::Saml::RequestHandler
11
11
  # request: Rack current request instance
12
- # saml_config: config/saml.yml
12
+ # config: config/saml.yml
13
13
  # metadata: specified idp entity in the config/metadata.yml
14
- def initialize(request, saml_config, metadata)
15
- @authn_request = (eval "Rack::Saml::#{saml_config['assertion_handler'].to_s.capitalize}Request").new(request, saml_config, metadata)
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, :saml_config, :metadata
4
+ attr_reader :request, :config, :metadata
5
5
 
6
- def initialize(request, saml_config, metadata)
6
+ def initialize(request, config, metadata)
7
7
  @request = request
8
- @saml_config = saml_config
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, saml_config, metadata)
10
- super(request, saml_config, metadata)
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
@@ -2,7 +2,7 @@ module Rack
2
2
  class Saml
3
3
  class OpensamlResponse < AbstractResponse
4
4
  # To be implemented
5
- def initialize(request, saml_config, metadata)
5
+ def initialize(request, config, metadata)
6
6
  end
7
7
 
8
8
  def redirect_uri
@@ -9,19 +9,33 @@ module Rack
9
9
 
10
10
  # Rack::Saml::ResponseHandler
11
11
  # request: Rack current request instance
12
- # saml_config: config/saml.yml
12
+ # config: config/saml.yml
13
13
  # metadata: specified idp entity in the config/metadata.yml
14
- def initialize(request, saml_config, metadata)
15
- @response = (eval "Rack::Saml::#{saml_config['assertion_handler'].to_s.capitalize}Response").new(request, saml_config, metadata)
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, attribute_map, opts = {})
19
- attribute_map.each do |attr_name, env_name|
20
- attribute = @response.attributes[attr_name]
21
- env[env_name] = attribute if !attribute.nil?
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
- if !opts[:shib_app_id].nil?
24
- env['Shib-Application-ID'] = opts[:shib_app_id]
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 default_saml_config
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[:saml_config].nil? || !::File.exists?(@opts[:saml_config])
42
- @opts[:saml_config] = default_saml_config
59
+ if @opts[:config].nil? || !::File.exists?(@opts[:config])
60
+ @opts[:config] = default_config
43
61
  end
44
- @saml_config = YAML.load_file(@opts[:saml_config])
45
- if @saml_config['assertion_handler'].nil?
46
- raise ArgumentError, "'assertion_handler' parameter should be specified in the :saml_config file"
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
- handler = RequestHandler.new(request, @saml_config, @metadata['idp_lists'][@saml_config['saml_idp']])
71
- return Rack::Response.new.tap { |r|
72
- r.redirect handler.authn_request.redirect_uri
73
- }.finish
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, @saml_config, @metadata['idp_lists'][@saml_config['saml_idp']])
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
- handler = ResponseHandler.new(request, @saml_config, @metadata['idp_lists'][@saml_config['saml_idp']])
86
- if handler.response.is_valid?
87
- handler.extract_attrs(env, @attribute_map, @opts)
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
- raise SamlAssertionError, "Invalid SAML response."
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
- if @saml_config['protected_path_regexp']
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 == @saml_config['metadata_path']
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
@@ -1,6 +1,6 @@
1
1
  require 'rack'
2
2
  module Rack
3
3
  module Saml
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
6
6
  end
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.1
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: 2011-12-29 00:00:00.000000000Z
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: &2153190660 !ruby/object:Gem::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: *2153190660
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