libsaml 2.0.5
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.
- checksums.yaml +15 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +91 -0
- data/Rakefile +33 -0
- data/lib/saml.rb +142 -0
- data/lib/saml/artifact.rb +51 -0
- data/lib/saml/artifact_resolve.rb +10 -0
- data/lib/saml/artifact_response.rb +9 -0
- data/lib/saml/assertion.rb +67 -0
- data/lib/saml/authn_request.rb +34 -0
- data/lib/saml/base.rb +47 -0
- data/lib/saml/bindings/http_artifact.rb +44 -0
- data/lib/saml/bindings/http_post.rb +29 -0
- data/lib/saml/bindings/http_redirect.rb +100 -0
- data/lib/saml/bindings/soap.rb +31 -0
- data/lib/saml/complex_types/endpoint_type.rb +17 -0
- data/lib/saml/complex_types/indexed_endpoint_type.rb +15 -0
- data/lib/saml/complex_types/request_abstract_type.rb +57 -0
- data/lib/saml/complex_types/sso_descriptor_type.rb +48 -0
- data/lib/saml/complex_types/status_response_type.rb +29 -0
- data/lib/saml/config.rb +49 -0
- data/lib/saml/elements/attribute.rb +24 -0
- data/lib/saml/elements/attribute_statement.rb +26 -0
- data/lib/saml/elements/audience_restriction.rb +12 -0
- data/lib/saml/elements/authn_context.rb +13 -0
- data/lib/saml/elements/authn_statement.rb +25 -0
- data/lib/saml/elements/conditions.rb +24 -0
- data/lib/saml/elements/contact_person.rb +33 -0
- data/lib/saml/elements/entities_descriptor.rb +27 -0
- data/lib/saml/elements/entity_descriptor.rb +37 -0
- data/lib/saml/elements/idp_sso_descriptor.rb +23 -0
- data/lib/saml/elements/key_descriptor.rb +34 -0
- data/lib/saml/elements/key_descriptor/key_info.rb +30 -0
- data/lib/saml/elements/key_descriptor/key_info/x509_data.rb +34 -0
- data/lib/saml/elements/name_id.rb +14 -0
- data/lib/saml/elements/organization.rb +16 -0
- data/lib/saml/elements/requested_authn_context.rb +28 -0
- data/lib/saml/elements/signature.rb +33 -0
- data/lib/saml/elements/signature/canonicalization_method.rb +19 -0
- data/lib/saml/elements/signature/digest_method.rb +19 -0
- data/lib/saml/elements/signature/inclusive_namespaces.rb +20 -0
- data/lib/saml/elements/signature/key_info.rb +14 -0
- data/lib/saml/elements/signature/reference.rb +23 -0
- data/lib/saml/elements/signature/signature_method.rb +19 -0
- data/lib/saml/elements/signature/signed_info.rb +24 -0
- data/lib/saml/elements/signature/transform.rb +19 -0
- data/lib/saml/elements/signature/transforms.rb +21 -0
- data/lib/saml/elements/sp_sso_descriptor.rb +27 -0
- data/lib/saml/elements/status.rb +15 -0
- data/lib/saml/elements/status_code.rb +42 -0
- data/lib/saml/elements/sub_status_code.rb +14 -0
- data/lib/saml/elements/subject.rb +38 -0
- data/lib/saml/elements/subject_confirmation.rb +30 -0
- data/lib/saml/elements/subject_confirmation_data.rb +23 -0
- data/lib/saml/elements/subject_locality.rb +12 -0
- data/lib/saml/encoding.rb +35 -0
- data/lib/saml/logout_request.rb +10 -0
- data/lib/saml/logout_response.rb +11 -0
- data/lib/saml/provider.rb +85 -0
- data/lib/saml/provider_stores/file.rb +33 -0
- data/lib/saml/response.rb +21 -0
- data/lib/saml/util.rb +51 -0
- data/lib/saml/version.rb +3 -0
- data/lib/saml/xml_helpers.rb +34 -0
- data/lib/tasks/saml_tasks.rake +4 -0
- metadata +195 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Saml
|
2
|
+
class AuthnRequest
|
3
|
+
include Saml::ComplexTypes::RequestAbstractType
|
4
|
+
|
5
|
+
tag 'AuthnRequest'
|
6
|
+
attribute :force_authn, Boolean, :tag => "ForceAuthn"
|
7
|
+
attribute :assertion_consumer_service_index, Integer, :tag => "AssertionConsumerServiceIndex"
|
8
|
+
attribute :assertion_consumer_service_url, String, :tag => "AssertionConsumerServiceURL"
|
9
|
+
attribute :attribute_consuming_service_index, Integer, :tag => "AttributeConsumingServiceIndex"
|
10
|
+
attribute :protocol_binding, String, :tag => "ProtocolBinding"
|
11
|
+
attribute :provider_name, String, :tag => "ProviderName"
|
12
|
+
|
13
|
+
has_one :requested_authn_context, Saml::Elements::RequestedAuthnContext
|
14
|
+
|
15
|
+
validates :force_authn, :inclusion => [true, false, nil]
|
16
|
+
validates :assertion_consumer_service_index, :numericality => true, :if => "assertion_consumer_service_index.present?"
|
17
|
+
|
18
|
+
validate :check_assertion_consumer_service
|
19
|
+
|
20
|
+
def assertion_url
|
21
|
+
return assertion_consumer_service_url if assertion_consumer_service_url
|
22
|
+
provider.assertion_consumer_service_url(assertion_consumer_service_index) if assertion_consumer_service_index
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def check_assertion_consumer_service
|
28
|
+
if assertion_consumer_service_index.present?
|
29
|
+
errors.add(:assertion_consumer_service_url, :must_be_blank) if @assertion_consumer_service_url.present?
|
30
|
+
errors.add(:protocol_binding, :must_be_blank) if protocol_binding.present?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/saml/base.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'happymapper'
|
2
|
+
|
3
|
+
module Saml
|
4
|
+
module Base
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include ::HappyMapper
|
9
|
+
include ::ActiveModel::Validations
|
10
|
+
|
11
|
+
extend HappyMapperClassMethods
|
12
|
+
include HappyMapperInstanceMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module HappyMapperInstanceMethods
|
16
|
+
def initialize(attributes = {})
|
17
|
+
attributes.each do |key, value|
|
18
|
+
send("#{key}=", value) if respond_to?("#{key}=") && value.present?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_xml=(bool)
|
23
|
+
@from_xml = bool
|
24
|
+
end
|
25
|
+
|
26
|
+
def from_xml?
|
27
|
+
@from_xml
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module HappyMapperClassMethods
|
32
|
+
def parse(xml, options = {})
|
33
|
+
object = super
|
34
|
+
if object.is_a?(Array)
|
35
|
+
object.map { |x| x.from_xml = true }
|
36
|
+
elsif object
|
37
|
+
object.from_xml = true
|
38
|
+
end
|
39
|
+
object
|
40
|
+
rescue Nokogiri::XML::SyntaxError => e
|
41
|
+
raise Saml::Errors::UnparseableMessage.new(e.message)
|
42
|
+
rescue NoMethodError => e
|
43
|
+
raise Saml::Errors::UnparseableMessage.new(e.message)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Saml
|
2
|
+
module Bindings
|
3
|
+
class HTTPArtifact
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# @param [Saml::ArtifactResponse] artifact_response
|
7
|
+
def create_response_xml(artifact_response)
|
8
|
+
Saml::Util.sign_xml(artifact_response, :soap)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_url(location, artifact, options = {})
|
12
|
+
uri = URI.parse(location)
|
13
|
+
query = [uri.query, "SAMLart=#{CGI.escape(artifact.to_s)}"]
|
14
|
+
|
15
|
+
query << "RelayState=#{CGI.escape(options[:relay_state])}" if options[:relay_state]
|
16
|
+
|
17
|
+
uri.query = query.compact.join("&")
|
18
|
+
uri.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive_message(request)
|
22
|
+
raw_xml = request.body.dup.read
|
23
|
+
artifact_resolve = Saml::ArtifactResolve.parse(raw_xml, single: true)
|
24
|
+
|
25
|
+
Saml::Util.verify_xml(artifact_resolve, raw_xml)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve(request, location)
|
29
|
+
artifact = request.params["SAMLart"]
|
30
|
+
artifact_resolve = Saml::ArtifactResolve.new(artifact: artifact, destination: location)
|
31
|
+
|
32
|
+
response = Saml::Util.post(location, Saml::Util.sign_xml(artifact_resolve, :soap))
|
33
|
+
|
34
|
+
if response.code == 200
|
35
|
+
artifact_response = Saml::ArtifactResponse.parse(response.body, single: true)
|
36
|
+
verified_artifact_response = Saml::Util.verify_xml(artifact_response, response.body)
|
37
|
+
|
38
|
+
verified_artifact_response.response if artifact_response.success?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Saml
|
2
|
+
module Bindings
|
3
|
+
class HTTPPost
|
4
|
+
class << self
|
5
|
+
def create_form_attributes(message, options = {})
|
6
|
+
param = message.is_a?(Saml::ComplexTypes::StatusResponseType) ? "SAMLResponse" : "SAMLRequest"
|
7
|
+
|
8
|
+
xml = Saml::Util.sign_xml(message)
|
9
|
+
|
10
|
+
variables = {}
|
11
|
+
variables[param] = Saml::Encoding.encode_64(xml)
|
12
|
+
variables["RelayState"] = options[:relay_state] if options[:relay_state]
|
13
|
+
|
14
|
+
{
|
15
|
+
location: message.destination,
|
16
|
+
variables: variables
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def receive_message(request, type)
|
21
|
+
message = Saml::Encoding.decode_64(request.params["SAMLRequest"] || request.params["SAMLResponse"])
|
22
|
+
request_or_response = Saml.parse_message(message, type)
|
23
|
+
|
24
|
+
Saml::Util.verify_xml(request_or_response, message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Saml
|
2
|
+
module Bindings
|
3
|
+
class HTTPRedirect
|
4
|
+
class << self
|
5
|
+
def create_url(request_or_response, options = {})
|
6
|
+
options[:signature_algorithm] ||= 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
|
7
|
+
new(request_or_response, options).create_url
|
8
|
+
end
|
9
|
+
|
10
|
+
def receive_message(http_request, options = {})
|
11
|
+
options[:signature] = Saml::Encoding.decode_64(http_request.params["Signature"] || "")
|
12
|
+
options[:signature_algorithm] = http_request.params["SigAlg"]
|
13
|
+
options[:relay_state] = http_request.params["RelayState"]
|
14
|
+
|
15
|
+
request_or_response = parse_request_or_response(options.delete(:type), http_request.params)
|
16
|
+
|
17
|
+
redirect_binding = new(request_or_response, options)
|
18
|
+
query_string = URI.parse(http_request.url).query
|
19
|
+
|
20
|
+
redirect_binding.verify_signature(query_string) if request_or_response.provider.authn_requests_signed?
|
21
|
+
|
22
|
+
request_or_response
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_request_or_response(type, params)
|
28
|
+
message = decode_message(params["SAMLRequest"])
|
29
|
+
|
30
|
+
Saml.parse_message(message, type)
|
31
|
+
end
|
32
|
+
|
33
|
+
def decode_message(message)
|
34
|
+
Saml::Encoding.decode_gzip(Saml::Encoding.decode_64(message)).gsub("\n", "")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :request_or_response, :signature_algorithm, :relay_state, :signature
|
39
|
+
|
40
|
+
def initialize(request_or_response, options = {})
|
41
|
+
@request_or_response = request_or_response
|
42
|
+
@signature_algorithm = options[:signature_algorithm]
|
43
|
+
@relay_state = options[:relay_state]
|
44
|
+
@signature = options[:signature]
|
45
|
+
end
|
46
|
+
|
47
|
+
def verify_signature(query)
|
48
|
+
unless request_or_response.provider.verify(signature_algorithm, signature, parse_signature_params(query))
|
49
|
+
raise Saml::Errors::SignatureInvalid.new
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_url
|
54
|
+
[request_or_response.destination, signed_params].join("?")
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
|
60
|
+
def parse_signature_params(query)
|
61
|
+
params = {}
|
62
|
+
query.split(/[&;]/).each do |pairs|
|
63
|
+
key, value = pairs.split('=', 2)
|
64
|
+
params[key] = value
|
65
|
+
end
|
66
|
+
|
67
|
+
relay_state = params["RelayState"] ? "&RelayState=#{params['RelayState']}" : ""
|
68
|
+
"SAMLRequest=#{params['SAMLRequest']}#{relay_state}&SigAlg=#{params['SigAlg']}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def encoded_message
|
72
|
+
Saml::Encoding.encode_64(Saml::Encoding.encode_gzip(request_or_response.to_xml))
|
73
|
+
end
|
74
|
+
|
75
|
+
def encoded_params
|
76
|
+
params.collect do |key, value|
|
77
|
+
"#{key}=#{CGI.escape(value)}"
|
78
|
+
end.join('&')
|
79
|
+
end
|
80
|
+
|
81
|
+
def params
|
82
|
+
params = {}
|
83
|
+
|
84
|
+
params["SAMLRequest"] = encoded_message
|
85
|
+
params["RelayState"] = relay_state if relay_state
|
86
|
+
params["SigAlg"] = signature_algorithm if signature_algorithm
|
87
|
+
|
88
|
+
params
|
89
|
+
end
|
90
|
+
|
91
|
+
def signed_params
|
92
|
+
signature = request_or_response.provider.sign(signature_algorithm, encoded_params)
|
93
|
+
|
94
|
+
encoded_signature = CGI.escape(Saml::Encoding.encode_64(signature))
|
95
|
+
|
96
|
+
"#{encoded_params}&Signature=#{encoded_signature}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Saml
|
2
|
+
module Bindings
|
3
|
+
class SOAP
|
4
|
+
class << self
|
5
|
+
def create_response_xml(response)
|
6
|
+
Saml::Util.sign_xml(response, :soap)
|
7
|
+
end
|
8
|
+
|
9
|
+
def post_message(message, response_type)
|
10
|
+
signed_message = Saml::Util.sign_xml(message, :soap)
|
11
|
+
|
12
|
+
http_response = Saml::Util.post(message.destination, signed_message)
|
13
|
+
|
14
|
+
if http_response.code == 200
|
15
|
+
response = Saml.parse_message(http_response.body, response_type)
|
16
|
+
Saml::Util.verify_xml(response, http_response.body)
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def receive_message(request, type)
|
23
|
+
raw_xml = request.body.dup.read
|
24
|
+
message = Saml.parse_message(raw_xml, type)
|
25
|
+
|
26
|
+
Saml::Util.verify_xml(message, raw_xml)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Saml
|
2
|
+
module ComplexTypes
|
3
|
+
module EndpointType
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include Saml::Base
|
6
|
+
|
7
|
+
included do
|
8
|
+
namespace 'md'
|
9
|
+
|
10
|
+
attribute :binding, String, :tag => "Binding"
|
11
|
+
attribute :location, String, :tag => "Location"
|
12
|
+
|
13
|
+
validates :binding, :location, :presence => true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Saml
|
2
|
+
module ComplexTypes
|
3
|
+
module IndexedEndpointType
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include EndpointType
|
6
|
+
|
7
|
+
included do
|
8
|
+
attribute :index, Integer, :tag => "index"
|
9
|
+
attribute :is_default, HappyMapper::Boolean, :tag => "isDefault"
|
10
|
+
|
11
|
+
validates :index, :presence => true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'happymapper'
|
2
|
+
|
3
|
+
module Saml
|
4
|
+
module ComplexTypes
|
5
|
+
module RequestAbstractType
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include Saml::Base
|
8
|
+
include Saml::XMLHelpers
|
9
|
+
|
10
|
+
included do
|
11
|
+
register_namespace 'samlp', Saml::SAMLP_NAMESPACE
|
12
|
+
register_namespace 'saml', Saml::SAML_NAMESPACE
|
13
|
+
namespace 'samlp'
|
14
|
+
|
15
|
+
attribute :_id, String, :tag => 'ID'
|
16
|
+
attribute :version, String, :tag => "Version"
|
17
|
+
attribute :issue_instant, Time, :tag => "IssueInstant", :on_save => lambda { |val| val.utc.xmlschema }
|
18
|
+
|
19
|
+
attribute :destination, String, :tag => "Destination"
|
20
|
+
element :issuer, String, :namespace => 'saml', :tag => "Issuer"
|
21
|
+
|
22
|
+
has_one :signature, Saml::Elements::Signature, :tag => "Signature"
|
23
|
+
|
24
|
+
validates :_id, :version, :issue_instant, :presence => true
|
25
|
+
|
26
|
+
validates :version, inclusion: %w(2.0)
|
27
|
+
validate :check_issue_instant, :if => "issue_instant.present?"
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(*args)
|
31
|
+
super(*args)
|
32
|
+
@_id ||= Saml.generate_id
|
33
|
+
@issue_instant ||= Time.now
|
34
|
+
@issuer ||= Saml::Config.entity_id
|
35
|
+
@version ||= Saml::SAML_VERSION
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_signature
|
39
|
+
self.signature = Saml::Elements::Signature.new(uri: "##{self._id}")
|
40
|
+
x509certificate = OpenSSL::X509::Certificate.new(provider.certificate) rescue nil
|
41
|
+
self.signature.key_info = Saml::Elements::KeyDescriptor::KeyInfo.new(x509certificate.to_pem) if x509certificate
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Saml::Provider]
|
45
|
+
def provider
|
46
|
+
Saml.provider(issuer)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def check_issue_instant
|
52
|
+
errors.add(:issue_instant, :too_old) if issue_instant < Time.now - Saml::Config.max_issue_instant_offset.minutes
|
53
|
+
errors.add(:issue_instant, :too_new) if issue_instant > Time.now + Saml::Config.max_issue_instant_offset.minutes
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Saml
|
2
|
+
module ComplexTypes
|
3
|
+
module SSODescriptorType
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include Saml::Base
|
6
|
+
|
7
|
+
class ArtifactResolutionService
|
8
|
+
include Saml::ComplexTypes::IndexedEndpointType
|
9
|
+
|
10
|
+
tag 'ArtifactResolutionService'
|
11
|
+
namespace 'md'
|
12
|
+
end
|
13
|
+
|
14
|
+
class SingleLogoutService
|
15
|
+
include Saml::ComplexTypes::EndpointType
|
16
|
+
|
17
|
+
tag 'SingleLogoutService'
|
18
|
+
namespace 'md'
|
19
|
+
end
|
20
|
+
|
21
|
+
included do
|
22
|
+
namespace 'md'
|
23
|
+
|
24
|
+
PROTOCOL_SUPPORT_ENUMERATION = "urn:oasis:names:tc:SAML:2.0:protocol" unless defined?(PROTOCOL_SUPPORT_ENUMERATION)
|
25
|
+
|
26
|
+
attribute :protocol_support_enumeration, String, :tag => "protocolSupportEnumeration"
|
27
|
+
attribute :valid_until, Time, :tag => "validUntil"
|
28
|
+
attribute :cache_duration, Integer, :tag => "cacheDuration"
|
29
|
+
attribute :error_url, String, :tag => "errorURL"
|
30
|
+
|
31
|
+
has_many :key_descriptors, Saml::Elements::KeyDescriptor
|
32
|
+
|
33
|
+
has_many :artifact_resolution_services, ArtifactResolutionService
|
34
|
+
has_many :single_logout_services, SingleLogoutService
|
35
|
+
|
36
|
+
validates :protocol_support_enumeration, :presence => true, :inclusion => [PROTOCOL_SUPPORT_ENUMERATION]
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(*args)
|
40
|
+
super(*args)
|
41
|
+
@single_logout_services ||= []
|
42
|
+
@key_descriptors ||= []
|
43
|
+
@artifact_resolution_services ||= []
|
44
|
+
@protocol_support_enumeration ||= PROTOCOL_SUPPORT_ENUMERATION
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'happymapper'
|
2
|
+
|
3
|
+
module Saml
|
4
|
+
module ComplexTypes
|
5
|
+
module StatusResponseType
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
include RequestAbstractType
|
9
|
+
|
10
|
+
included do
|
11
|
+
attribute :in_response_to, String, :tag => 'InResponseTo'
|
12
|
+
has_one :status, Saml::Elements::Status
|
13
|
+
|
14
|
+
validates :in_response_to, :status, :presence => true
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
options = args.extract_options!
|
19
|
+
@status = Saml::Elements::Status.new(:status_code => Saml::Elements::StatusCode.new(:value => options.delete(:status_value),
|
20
|
+
:sub_status_value => options.delete(:sub_status_value)))
|
21
|
+
super(*(args << options))
|
22
|
+
end
|
23
|
+
|
24
|
+
def success?
|
25
|
+
status.status_code.success?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|