scashin133-rsaml 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +10 -0
- data/.gitignore +2 -0
- data/LICENSE +0 -0
- data/README +13 -0
- data/Rakefile +141 -0
- data/lib/rsaml/action.rb +57 -0
- data/lib/rsaml/action_namespace.rb +63 -0
- data/lib/rsaml/advice.rb +34 -0
- data/lib/rsaml/assertion.rb +192 -0
- data/lib/rsaml/attribute.rb +76 -0
- data/lib/rsaml/audience.rb +19 -0
- data/lib/rsaml/authentication_context.rb +34 -0
- data/lib/rsaml/authn_context/README +1 -0
- data/lib/rsaml/authn_context/authentication_context_declaration.rb +42 -0
- data/lib/rsaml/authn_context/identification.rb +10 -0
- data/lib/rsaml/authn_context/physical_verification.rb +24 -0
- data/lib/rsaml/condition.rb +13 -0
- data/lib/rsaml/conditions.rb +107 -0
- data/lib/rsaml/encrypted.rb +12 -0
- data/lib/rsaml/errors.rb +16 -0
- data/lib/rsaml/evidence.rb +21 -0
- data/lib/rsaml/ext/string.rb +5 -0
- data/lib/rsaml/identifier/base.rb +23 -0
- data/lib/rsaml/identifier/issuer.rb +28 -0
- data/lib/rsaml/identifier/name.rb +55 -0
- data/lib/rsaml/identifier.rb +9 -0
- data/lib/rsaml/parser.rb +23 -0
- data/lib/rsaml/protocol/artifact_resolve.rb +14 -0
- data/lib/rsaml/protocol/assertion_id_request.rb +18 -0
- data/lib/rsaml/protocol/authn_request.rb +91 -0
- data/lib/rsaml/protocol/idp_entry.rb +18 -0
- data/lib/rsaml/protocol/idp_list.rb +28 -0
- data/lib/rsaml/protocol/message.rb +65 -0
- data/lib/rsaml/protocol/name_id_policy.rb +31 -0
- data/lib/rsaml/protocol/query/attribute_query.rb +56 -0
- data/lib/rsaml/protocol/query/authn_query.rb +30 -0
- data/lib/rsaml/protocol/query/authz_decision_query.rb +40 -0
- data/lib/rsaml/protocol/query/subject_query.rb +22 -0
- data/lib/rsaml/protocol/query.rb +12 -0
- data/lib/rsaml/protocol/request.rb +27 -0
- data/lib/rsaml/protocol/requested_authn_context.rb +34 -0
- data/lib/rsaml/protocol/response.rb +56 -0
- data/lib/rsaml/protocol/scoping.rb +33 -0
- data/lib/rsaml/protocol/status.rb +38 -0
- data/lib/rsaml/protocol/status_code.rb +84 -0
- data/lib/rsaml/protocol.rb +21 -0
- data/lib/rsaml/proxy_restriction.rb +30 -0
- data/lib/rsaml/statement/attribute_statement.rb +27 -0
- data/lib/rsaml/statement/authentication_statement.rb +57 -0
- data/lib/rsaml/statement/authorization_decision_statement.rb +53 -0
- data/lib/rsaml/statement/base.rb +9 -0
- data/lib/rsaml/statement.rb +10 -0
- data/lib/rsaml/subject.rb +37 -0
- data/lib/rsaml/subject_confirmation.rb +34 -0
- data/lib/rsaml/subject_confirmation_data.rb +45 -0
- data/lib/rsaml/subject_locality.rb +27 -0
- data/lib/rsaml/validatable.rb +21 -0
- data/lib/rsaml/version.rb +9 -0
- data/lib/rsaml.rb +51 -0
- data/lib/xml_enc.rb +3 -0
- data/lib/xml_sig/canonicalization_method.rb +43 -0
- data/lib/xml_sig/key_info.rb +55 -0
- data/lib/xml_sig/reference.rb +57 -0
- data/lib/xml_sig/signature.rb +29 -0
- data/lib/xml_sig/signature_method.rb +20 -0
- data/lib/xml_sig/signed_info.rb +27 -0
- data/lib/xml_sig/transform.rb +37 -0
- data/lib/xml_sig.rb +11 -0
- data/scashin133-rsaml.gemspec +180 -0
- data/test/action_namespace_test.rb +93 -0
- data/test/action_test.rb +51 -0
- data/test/advice_test.rb +25 -0
- data/test/assertion_test.rb +192 -0
- data/test/attribute_test.rb +60 -0
- data/test/authentication_context_test.rb +26 -0
- data/test/conditions_test.rb +84 -0
- data/test/evidence_test.rb +33 -0
- data/test/identifier_test.rb +22 -0
- data/test/issuer_test.rb +32 -0
- data/test/name_test.rb +32 -0
- data/test/parser_test.rb +32 -0
- data/test/protocol/assertion_id_request_test.rb +19 -0
- data/test/protocol/attribute_query_test.rb +30 -0
- data/test/protocol/authn_query_test.rb +20 -0
- data/test/protocol/authn_request_test.rb +56 -0
- data/test/protocol/authz_decision_query_test.rb +31 -0
- data/test/protocol/idp_list_test.rb +15 -0
- data/test/protocol/request_test.rb +66 -0
- data/test/protocol/response_test.rb +68 -0
- data/test/protocol/scoping_test.rb +20 -0
- data/test/protocol/status_code_test.rb +34 -0
- data/test/protocol/status_test.rb +16 -0
- data/test/proxy_restriction_test.rb +20 -0
- data/test/rsaml_test.rb +12 -0
- data/test/sample_data/attribute_query.xml +8 -0
- data/test/statement_test.rb +101 -0
- data/test/subject_locality_test.rb +27 -0
- data/test/subject_test.rb +44 -0
- data/test/test_helper.rb +16 -0
- data/test/xml_sig/canonicalization_test.rb +19 -0
- data/test/xml_sig/iso-8859-1.txt +1 -0
- metadata +206 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# Tailors the name identifier in the subjects of assertions resulting from an authentication request.
|
4
|
+
class NameIdPolicy
|
5
|
+
# Specifies the URI reference corresponding to a name identifier format
|
6
|
+
attr_accessor :format
|
7
|
+
|
8
|
+
# Optionally specifies that the assertion subject's identifier be returned (or created) in the namespace of
|
9
|
+
# a service provider other than the requester, or in the namespace of an affiliation group of service
|
10
|
+
# providers.
|
11
|
+
attr_accessor :sp_name_qualifier
|
12
|
+
|
13
|
+
# A Boolean value used to indicate whether the identity provider is allowed, in the course of fulfilling the
|
14
|
+
# request, to create a new identifier to represent the principal. Defaults to "false". When "false", the
|
15
|
+
# requester constrains the identity provider to only issue an assertion to it if an acceptable identifier for
|
16
|
+
# the principal has already been established. Note that this does not prevent the identity provider from
|
17
|
+
# creating such identifiers outside the context of this specific request (for example, in advance for a
|
18
|
+
# large number of principals).
|
19
|
+
attr_accessor :allow_create
|
20
|
+
|
21
|
+
# Construct an XML fragment representing the name id policy
|
22
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
23
|
+
attributes = {}
|
24
|
+
attributes['Format'] = format unless format.nil?
|
25
|
+
attributes['SPNameQualifier'] = sp_name_qualifier unless sp_name_qualifier.nil?
|
26
|
+
attributes['AllowCreate'] = allow_create unless allow_create.nil?
|
27
|
+
xml.tag!('samlp:NameIDPolicy', attributes)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
module Query #:nodoc:
|
4
|
+
# used to make the query "Return the requested attributes for this subject." A successful response
|
5
|
+
# will be in the form of assertions containing attribute statements, to the extent allowed by policy.
|
6
|
+
class AttributeQuery < SubjectQuery
|
7
|
+
# Each attribute element specifies an attribute whose value(s) are to be returned. If no
|
8
|
+
# attributes are specified, it indicates that all attributes allowed by policy are requested.
|
9
|
+
# If a given attribute element contains one or more AttributeValue elements, then if that attribute
|
10
|
+
# is returned in the response, it MUST NOT contain any values that are not equal to the values
|
11
|
+
# specified in the query. In the absence of equality rules specified by particular profiles or
|
12
|
+
# attributes, equality is defined as an identical XML representation of the value. Each value in
|
13
|
+
# the array MUST be an Attribute instance.
|
14
|
+
def attributes
|
15
|
+
@attributes ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Validate the structure of the attribute query.
|
19
|
+
def validate
|
20
|
+
matched = {}
|
21
|
+
duplicated_attributes = []
|
22
|
+
attributes.each do |attribute|
|
23
|
+
if matched.has_key?(attribute.name) && matched[attribute.name] == attribute.name_format
|
24
|
+
duplicated_attributes << attribute.name unless duplicated_attributes.include?(attribute.name)
|
25
|
+
else
|
26
|
+
matched[attribute.name] = attribute.name_format
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if !duplicated_attributes.empty?
|
30
|
+
raise ValidationError, "An attribute with the same name and name format may only be specified once. The following attributes were specified multiple times: #{duplicated_attributes.join(',')}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Construct an XML fragment representing the attribute query
|
35
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
36
|
+
xml_attributes = {}
|
37
|
+
xml.tag!('samlp:AttributeQuery', xml_attributes) {
|
38
|
+
xml << subject.to_xml unless subject.nil?
|
39
|
+
attributes.each { |attribute| xml << attribute.to_xml }
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Construct an AttributeQuery instance from the XML Element.
|
44
|
+
def self.from_xml(element)
|
45
|
+
element = REXML::Document.new(element).root if element.is_a?(String)
|
46
|
+
subject = Subject.from_xml(element.get_elements('saml:Subject').first)
|
47
|
+
attribute_query = AttributeQuery.new(subject)
|
48
|
+
element.get_elements('saml:Attribute').each do |attribute_element|
|
49
|
+
attribute_query.attributes << Attribute.from_xml(attribute_element)
|
50
|
+
end
|
51
|
+
attribute_query
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
module Query #:nodoc:
|
4
|
+
# An AuthnQuery is used to make the query "What assertions containing authentication statements
|
5
|
+
# are available for this subject?" A successful response will contain one or more assertions containing
|
6
|
+
# authentication statements.
|
7
|
+
class AuthnQuery < SubjectQuery
|
8
|
+
# If present, specifies a filter for possible responses. Such a query asks the question "What assertions
|
9
|
+
# containing authentication statements do you have for this subject within the context of the supplied
|
10
|
+
# session information?" The value of this attribute MUST be a string.
|
11
|
+
attr_accessor :session_index
|
12
|
+
|
13
|
+
# If present, specifies a filter for possible responses. Such a query asks the question "What assertions
|
14
|
+
# containing authentication statements do you have for this subject that satisfy the authentication
|
15
|
+
# context requirements in this element?" The value of this attribute MUST be a RequestedAuthnContext
|
16
|
+
# instance.
|
17
|
+
attr_accessor :requested_authn_context
|
18
|
+
|
19
|
+
# Construct an XML fragment representing the authn query
|
20
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
21
|
+
attributes = {}
|
22
|
+
attributes['SessionIndex'] = session_index unless session_index.nil?
|
23
|
+
xml.tag!('samlp:AuthnQuery', attributes) {
|
24
|
+
xml << subject.to_xml unless subject.nil?
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Source code for the RSAML::Protocol::Query::AuthzDecisionQuery class.
|
2
|
+
|
3
|
+
module RSAML #:nodoc:
|
4
|
+
module Protocol #:nodoc:
|
5
|
+
module Query #:nodoc:
|
6
|
+
# Used to make the query "Should these actions on this resource be allowed for this subject,
|
7
|
+
# given this evidence?" A successful response will be in the form of assertions containing
|
8
|
+
# authorization decision statements.
|
9
|
+
class AuthzDecisionQuery < SubjectQuery
|
10
|
+
# A URI reference indicating the resource for which authorization is requested.
|
11
|
+
attr_accessor :resource
|
12
|
+
|
13
|
+
# The actions for which authorization is requested.
|
14
|
+
def actions
|
15
|
+
@actions ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
# A set of assertions that the SAML authority MAY rely on in making its authorization decision.
|
19
|
+
attr_accessor :evidence
|
20
|
+
|
21
|
+
# Validate the query structure.
|
22
|
+
def validate
|
23
|
+
raise ValidationError, "Resource is required" if resource.nil?
|
24
|
+
raise ValidationError, "At least one action is required" if actions.empty?
|
25
|
+
actions.each { |action| action.validate }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Construct an XML fragment representing the authorization decision query
|
29
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
30
|
+
attributes = {'Resource' => resource}
|
31
|
+
xml.tag!('samlp:AuthzDecisionQuery', attributes) {
|
32
|
+
xml << subject.to_xml unless subject.nil?
|
33
|
+
actions.each { |action| xml << action.to_xml }
|
34
|
+
xml << evidence.to_xml unless evidence.nil?
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
module Query #:nodoc:
|
4
|
+
# Extension point that allows new SAML queries to be defined that specify a single SAML subject.
|
5
|
+
# This class should not be instantiated directly.
|
6
|
+
class SubjectQuery < RSAML::Protocol::Request
|
7
|
+
# The subject
|
8
|
+
attr_accessor :subject
|
9
|
+
|
10
|
+
# Initialize the subject query
|
11
|
+
def initialize(subject)
|
12
|
+
@subject = subject
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validate the subject query structure.
|
16
|
+
def validate
|
17
|
+
raise ValidationError, "Subject is required" if subject.nil?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# Module containing SAML query classes.
|
4
|
+
module Query
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rsaml/protocol/query/subject_query'
|
10
|
+
require 'rsaml/protocol/query/authn_query'
|
11
|
+
require 'rsaml/protocol/query/attribute_query'
|
12
|
+
require 'rsaml/protocol/query/authz_decision_query'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# A SAML request
|
4
|
+
class Request < Message
|
5
|
+
# Generate a Response instance with the given status code. The response's in_response_to attribute
|
6
|
+
# will be set to the ID of the request.
|
7
|
+
def respond(status)
|
8
|
+
response = Response.new(status)
|
9
|
+
response.in_response_to = id
|
10
|
+
response
|
11
|
+
end
|
12
|
+
|
13
|
+
# Construct an XML fragment representing the request
|
14
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
15
|
+
attributes = {'ID' => id, 'Version' => version, 'IssueInstant' => issue_instant.xmlschema}
|
16
|
+
attributes['Destination'] = destination unless destination.nil?
|
17
|
+
attributes['Consent'] = consent unless consent.nil?
|
18
|
+
attributes = add_xmlns(attributes)
|
19
|
+
xml.tag!('samlp:Request', attributes) {
|
20
|
+
xml << issuer.to_xml unless issuer.nil?
|
21
|
+
xml << signature.to_xml unless signature.nil?
|
22
|
+
# TODO: add extensions support
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# Specifies the authentication context requirements of authentication statements returned in
|
4
|
+
# response to a request or query.
|
5
|
+
class RequestedAuthnContext
|
6
|
+
# List of available comparison values
|
7
|
+
def self.comparisons
|
8
|
+
@comparisons ||= ['exact','minimum','maximum','better']
|
9
|
+
end
|
10
|
+
|
11
|
+
# Authentication context references, either AuthnContextDeclRef or AuthnContextClassRef.
|
12
|
+
def authn_context_refs
|
13
|
+
@authn_context_refs ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Specifies the comparison method used to evaluate the requested context classes or statements, one
|
17
|
+
# of "exact", "minimum", "maximum", or "better". The default is "exact"
|
18
|
+
def comparison
|
19
|
+
@comparison ||= 'exact'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Validate the structure of the requested authn context
|
23
|
+
def validate
|
24
|
+
raise ValidationError, "Unknown comparison type: #{comparison}" unless RequestedAuthContext.comparisons.include?(comparison)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Construct an XML fragment representing the requested authn context
|
28
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
29
|
+
attributes = {'Comparison' => comparison}
|
30
|
+
xml.tag!('samlp:RequestedAuthnContext')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# A SAML response
|
4
|
+
class Response < Message
|
5
|
+
# A reference to the identifier of the request to which the response corresponds, if any. If the response
|
6
|
+
# is not generated in response to a request, or if the ID attribute value of a request cannot be
|
7
|
+
# determined (for example, the request is malformed), then this attribute MUST NOT be present.
|
8
|
+
# Otherwise, it MUST be present and its value MUST match the value of the corresponding request's
|
9
|
+
# ID attribute.
|
10
|
+
attr_accessor :in_response_to
|
11
|
+
|
12
|
+
# A code representing the status of the corresponding request.
|
13
|
+
attr_accessor :status
|
14
|
+
|
15
|
+
# Initialize the Response instance
|
16
|
+
def initialize(status)
|
17
|
+
super()
|
18
|
+
@status = status
|
19
|
+
end
|
20
|
+
|
21
|
+
# SAML assertions
|
22
|
+
def assertions
|
23
|
+
@assertions ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
# SAML encrypted assertions
|
27
|
+
def encrypted_assertions
|
28
|
+
@encrypted_assertions ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
# Validate the request structure
|
32
|
+
def validate
|
33
|
+
super
|
34
|
+
raise ValidationError, "Status must be specified" if status.nil?
|
35
|
+
raise ValidationError, "Status must be a RSAML::Protocol::Status instance" unless status.is_a?(Status)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Construct an XML fragment representing the request
|
39
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
40
|
+
attributes = {'ID' => id, 'Version' => version, 'IssueInstant' => issue_instant.xmlschema}
|
41
|
+
attributes['InResponseTo'] = in_response_to unless in_response_to.nil?
|
42
|
+
attributes['Destination'] = destination unless destination.nil?
|
43
|
+
attributes['Consent'] = consent unless consent.nil?
|
44
|
+
attributes = add_xmlns(attributes)
|
45
|
+
xml.tag!('samlp:Response', attributes) {
|
46
|
+
xml << issuer.to_xml unless issuer.nil?
|
47
|
+
xml << signature.to_xml unless signature.nil?
|
48
|
+
# TODO: add extensions support
|
49
|
+
xml << status.to_xml unless status.nil?
|
50
|
+
assertions.each { |assertion| xml << assertion.to_xml }
|
51
|
+
encrypted_assertions.each { |encrypted_assertion| xml << encrypted_assertion.to_xml }
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# specifies the identity providers trusted by the requester to authenticate the presenter, as well as
|
4
|
+
# limitations and context related to proxying of the <AuthnRequest> message to subsequent identity
|
5
|
+
# providers by the responder.
|
6
|
+
class Scoping
|
7
|
+
# Specifies the number of proxying indirections permissible between the identity provider that receives
|
8
|
+
# this <AuthnRequest> and the identity provider who ultimately authenticates the principal. A count of
|
9
|
+
# zero permits no proxying, while omitting this attribute expresses no such restriction.
|
10
|
+
attr_accessor :proxy_count
|
11
|
+
|
12
|
+
# An advisory list of identity providers and associated information that the requester deems acceptable
|
13
|
+
# to respond to the request.
|
14
|
+
attr_accessor :idp_list
|
15
|
+
|
16
|
+
# Identifies the set of requesting entities on whose behalf the requester is acting. Used to communicate
|
17
|
+
# the chain of requesters when proxying occurs.
|
18
|
+
def requestor_ids
|
19
|
+
@requestor_ids ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Construct an XML fragment representing the scoping
|
23
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
24
|
+
attributes = {}
|
25
|
+
attributes['ProxyCount'] = proxy_count if proxy_count
|
26
|
+
xml.tag!('samlp:Scoping', attributes) {
|
27
|
+
xml << idp_list.to_xml if idp_list
|
28
|
+
requestor_ids.each { |requestor_id| xml << requestor_id.to_xml }
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# A SAML status indicator.
|
4
|
+
class Status
|
5
|
+
# A code representing the status of the activity carried out in response to the corresponding request.
|
6
|
+
attr_accessor :status_code
|
7
|
+
|
8
|
+
# A message which MAY be returned to an operator.
|
9
|
+
attr_accessor :status_message
|
10
|
+
|
11
|
+
# Initialize the status with the given status code
|
12
|
+
def initialize(status_code)
|
13
|
+
@status_code = status_code
|
14
|
+
end
|
15
|
+
|
16
|
+
# Additional information concerning the status of the request. All objects in the collection must
|
17
|
+
# respond to the to_xml method.
|
18
|
+
def status_detail
|
19
|
+
@status_detail ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Validate the structure of the Status instance
|
23
|
+
def validate
|
24
|
+
raise ValidationError, "Status code required" if status_code.nil?
|
25
|
+
raise ValidationError, "Status code must be a RSAML::Protocol::StatusCode instance" unless status_code.is_a?(StatusCode)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Construct an XML fragment representing the request
|
29
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
30
|
+
xml.tag!('samlp:Status') {
|
31
|
+
xml << status_code.to_xml unless status_code.nil?
|
32
|
+
xml.tag!('StatusMessage', status_message) unless status_message.nil?
|
33
|
+
status_detail.each { |status_detail| xml << status_detail.to_xml }
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Protocol #:nodoc:
|
3
|
+
# A code or a set of nested codes representing the status of the corresponding request.
|
4
|
+
#
|
5
|
+
# More information on available status codes may be found in Section 3.2.2.2 of the SAML 2.0 Core
|
6
|
+
# specification.
|
7
|
+
class StatusCode
|
8
|
+
# Initialize the status code with the given value
|
9
|
+
def initialize(value)
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
# Constant respresenting the Success status
|
14
|
+
SUCCESS = StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:Success')
|
15
|
+
|
16
|
+
# Constant representing the Requestor status
|
17
|
+
REQUESTOR = StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:Requestor')
|
18
|
+
|
19
|
+
# Constant representing the Responder status
|
20
|
+
RESPONDER = StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:Responder')
|
21
|
+
|
22
|
+
# Constant representing the VersionMismatch status
|
23
|
+
VERSION_MISMATCH = StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:VersionMismatch')
|
24
|
+
|
25
|
+
# Hash of symbol/StatusCode pairs representing top-level status codes.
|
26
|
+
def self.top_level_status_codes
|
27
|
+
@top_level_status_codes ||= {
|
28
|
+
:success => SUCCESS,
|
29
|
+
:requestor => REQUESTOR,
|
30
|
+
:responder => RESPONDER,
|
31
|
+
:version_mismatch => VERSION_MISMATCH
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Hash of symbol/StatusCode pairs representing second-level status codes.
|
36
|
+
def self.second_level_status_codes
|
37
|
+
@second_level_status_codes ||= {
|
38
|
+
:authn_failed => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:AuthnFailed'),
|
39
|
+
:invalid_attr_name_or_value => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:InvalidAttrNameOrValue'),
|
40
|
+
:invalid_name_id_policy => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy'),
|
41
|
+
:no_authn_context => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext'),
|
42
|
+
:no_available_idp => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP'),
|
43
|
+
:no_passive => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:NoPassive'),
|
44
|
+
:no_supported_idp => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:NoSupportedIDP'),
|
45
|
+
:partial_logout => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:PartialLogout'),
|
46
|
+
:proxy_count_exceeded => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded'),
|
47
|
+
:request_denied => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:RequestDenied'),
|
48
|
+
:request_unsupported => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported'),
|
49
|
+
:request_version_deprecated => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated'),
|
50
|
+
:request_version_too_high => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooHigh'),
|
51
|
+
:request_version_too_low => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooLow'),
|
52
|
+
:resource_not_recognized => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:ResourceNotRecognized'),
|
53
|
+
:too_many_responses => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:TooManyResponse'),
|
54
|
+
:unknown_attr_profile => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:UnknownAttrProfile'),
|
55
|
+
:unknown_principal => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal'),
|
56
|
+
:unsupported_binding => StatusCode.new('urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding'),
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# The status code value. Value is a URI reference.
|
61
|
+
attr_accessor :value
|
62
|
+
|
63
|
+
# An optional child status code.
|
64
|
+
attr_accessor :status_code
|
65
|
+
|
66
|
+
def validate
|
67
|
+
raise ValidationError, "Value is required" if value.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
# Construct an XML fragment representing the request
|
71
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
72
|
+
attributes = {'Value' => value}
|
73
|
+
xml.tag!('samlp:StatusCode', attributes) {
|
74
|
+
xml << status_code.to_xml unless status_code.nil?
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return the value of the status code as a string.
|
79
|
+
def to_s
|
80
|
+
value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
# The protocol module contains request and response classes for the SAML protocol implementation
|
3
|
+
module Protocol
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rsaml/protocol/message'
|
8
|
+
require 'rsaml/protocol/status_code'
|
9
|
+
require 'rsaml/protocol/status'
|
10
|
+
require 'rsaml/protocol/request'
|
11
|
+
require 'rsaml/protocol/response'
|
12
|
+
|
13
|
+
require 'rsaml/protocol/name_id_policy'
|
14
|
+
require 'rsaml/protocol/scoping'
|
15
|
+
require 'rsaml/protocol/idp_list'
|
16
|
+
require 'rsaml/protocol/idp_entry'
|
17
|
+
|
18
|
+
require 'rsaml/protocol/assertion_id_request'
|
19
|
+
require 'rsaml/protocol/authn_request'
|
20
|
+
|
21
|
+
require 'rsaml/protocol/query'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
# Specifies limitations that the asserting party imposes on relying parties that in turn wish to act as asserting
|
3
|
+
# parties and issue subsequent assertions of their own on the basis of the information contained in the
|
4
|
+
# original assertion. A relying party acting as an asserting party MUST NOT issue an assertion that itself
|
5
|
+
# violates the restrictions specified in this condition on the basis of an assertion containing such a condition.
|
6
|
+
class ProxyRestriction
|
7
|
+
# Specifies the maximum number of indirections that the asserting party permits to exist between this
|
8
|
+
# assertion and an assertion which has ultimately been issued on the basis of it.
|
9
|
+
attr_accessor :count
|
10
|
+
|
11
|
+
def audiences
|
12
|
+
@audiences ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Validate the structure
|
16
|
+
def validate
|
17
|
+
raise ValidationError, "Count must be 0 or more if specified" if !count.nil? && count < 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# Construct an XML fragment representing the proxy restriction
|
21
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
22
|
+
attributes = {}
|
23
|
+
attributes['Count'] = count unless count.nil?
|
24
|
+
xml.tag!('saml:ProxyRestriction', attributes) {
|
25
|
+
audiences.each { |audience| xml << audience.to_xml }
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Statement #:nodoc:
|
3
|
+
# The assertion subject is associated with the supplied attributes.
|
4
|
+
class AttributeStatement < Base
|
5
|
+
# Specifies attributes of the assertion subject.
|
6
|
+
def attributes
|
7
|
+
@attributes ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
# Validate the structure of the attribute statement. Raises a validation error if:
|
11
|
+
#
|
12
|
+
# * Has no attributes specified
|
13
|
+
# * Any of the attributes are invalid
|
14
|
+
def validate
|
15
|
+
raise ValidationError, "At least one attribute must be specified" if @attributes.empty?
|
16
|
+
@attributes.each { |attribute| attribute.validate }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Construct an XML fragment representing the authentication statement
|
20
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
21
|
+
xml.tag!('saml:AttributeStatement') {
|
22
|
+
attributes.each { |attribute| xml << attribute.to_xml }
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Statement #:nodoc:
|
3
|
+
# The assertion subject was authenticated by a particular means at a particular time.
|
4
|
+
class AuthenticationStatement < Base
|
5
|
+
# Specifies the time at which the authentication took place. The time value is encoded in UTC
|
6
|
+
attr_accessor :authn_instant
|
7
|
+
|
8
|
+
# Specifies the index of a particular session between the principal identified by the subject and the
|
9
|
+
# authenticating authority. In general, any string value MAY be used as a SessionIndex value.
|
10
|
+
# However, when privacy is a consideration, care must be taken to ensure that the SessionIndex
|
11
|
+
# value does not invalidate other privacy mechanisms. Accordingly, the value SHOULD NOT be usable
|
12
|
+
# to correlate activity by a principal across different session participants.
|
13
|
+
attr_accessor :session_index
|
14
|
+
|
15
|
+
# Specifies a time instant at which the session between the principal identified by the subject and the
|
16
|
+
# SAML authority issuing this statement MUST be considered ended. The time value is encoded in
|
17
|
+
# UTCSpecifies
|
18
|
+
attr_accessor :session_not_on_or_after
|
19
|
+
|
20
|
+
# Specifies the DNS domain name and IP address for the system from which the assertion subject was
|
21
|
+
# apparently authenticated.
|
22
|
+
attr_accessor :subject_locality
|
23
|
+
|
24
|
+
# The authentication context.
|
25
|
+
attr_accessor :authn_context
|
26
|
+
|
27
|
+
# Initialize the statement
|
28
|
+
def initialize(authn_context)
|
29
|
+
@authn_context = authn_context
|
30
|
+
@authn_instant = Time.now.utc
|
31
|
+
end
|
32
|
+
|
33
|
+
# Validate the structure of the authentication statement. Raise a ValidationError if the
|
34
|
+
# statement is invalid.
|
35
|
+
def validate
|
36
|
+
if session_not_on_or_after && !session_not_on_or_after.utc?
|
37
|
+
raise ValidationError, "Session not on or after must be UTC"
|
38
|
+
end
|
39
|
+
raise ValidationError, "Authn context required" unless authn_context
|
40
|
+
raise ValidationError, "Authn instant required" unless authn_instant
|
41
|
+
raise ValidationError, "Authn instant must be UTC" unless authn_instant.utc?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Construct an XML fragment representing the authentication statement
|
45
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
46
|
+
validate
|
47
|
+
attributes = {'AuthnInstant' => authn_instant.xmlschema}
|
48
|
+
attributes['SessionIndex'] = session_index unless session_index.nil?
|
49
|
+
attributes['SessionNotOnOrAfter'] = session_not_on_or_after.xmlschema unless session_not_on_or_after.nil?
|
50
|
+
xml.tag!('saml:AuthnStatement', attributes) {
|
51
|
+
xml << authn_context.to_xml
|
52
|
+
xml << subject_locality.to_xml unless subject_locality.nil?
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RSAML #:nodoc:
|
2
|
+
module Statement #:nodoc:
|
3
|
+
# A request to allow the assertion subject to access the specified resource
|
4
|
+
# has been granted or denied.
|
5
|
+
class AuthorizationDecisionStatement < Base
|
6
|
+
# defines the possible values to be reported as the status of an authorization decision statement.
|
7
|
+
#
|
8
|
+
# Possible values are:
|
9
|
+
# * <tt>Permit</tt>: The specified action is permitted.
|
10
|
+
# * <tt>Deny</tt>: The specified action is denied.
|
11
|
+
# * <tt>Indeterminate</tt> The SAML authority cannot determine whether the specified action
|
12
|
+
# is permitted or denied.
|
13
|
+
def self.decision_types
|
14
|
+
%w(Permit Deny Indeterminate)
|
15
|
+
end
|
16
|
+
|
17
|
+
# A URI reference identifying the resource to which access authorization is sought.
|
18
|
+
# This attribute MAY have the value of the empty URI reference (""), and the meaning
|
19
|
+
# is defined to be "the start of the current document"
|
20
|
+
attr_accessor :resource
|
21
|
+
|
22
|
+
# The decision rendered by the SAML authority with respect to the specified resource.
|
23
|
+
attr_accessor :decision
|
24
|
+
|
25
|
+
# The set of actions authorized to be performed on the specified resource.
|
26
|
+
def actions
|
27
|
+
@actions ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
# A set of assertions that the SAML authority relied on in making the decision.
|
31
|
+
def evidence
|
32
|
+
@evidence ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Validate the structure
|
36
|
+
def validate
|
37
|
+
raise ValidationError, "Resource is required" if resource.nil?
|
38
|
+
raise ValidationError, "Decision is required" if decision.nil?
|
39
|
+
raise ValidationError, "One or more actions must be specified" if actions.empty?
|
40
|
+
actions.each { |action| action.validate }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Construct an XML fragment representing the authorization decision statement
|
44
|
+
def to_xml(xml=Builder::XmlMarkup.new)
|
45
|
+
attributes = {'Resource' => resource, 'Decision' => decision}
|
46
|
+
xml.tag!('saml:AuthzStatement', attributes) {
|
47
|
+
actions.each { |action| xml << action.to_xml }
|
48
|
+
evidence.each { |e| xml << e.to_xml }
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|