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.
Files changed (66) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +91 -0
  4. data/Rakefile +33 -0
  5. data/lib/saml.rb +142 -0
  6. data/lib/saml/artifact.rb +51 -0
  7. data/lib/saml/artifact_resolve.rb +10 -0
  8. data/lib/saml/artifact_response.rb +9 -0
  9. data/lib/saml/assertion.rb +67 -0
  10. data/lib/saml/authn_request.rb +34 -0
  11. data/lib/saml/base.rb +47 -0
  12. data/lib/saml/bindings/http_artifact.rb +44 -0
  13. data/lib/saml/bindings/http_post.rb +29 -0
  14. data/lib/saml/bindings/http_redirect.rb +100 -0
  15. data/lib/saml/bindings/soap.rb +31 -0
  16. data/lib/saml/complex_types/endpoint_type.rb +17 -0
  17. data/lib/saml/complex_types/indexed_endpoint_type.rb +15 -0
  18. data/lib/saml/complex_types/request_abstract_type.rb +57 -0
  19. data/lib/saml/complex_types/sso_descriptor_type.rb +48 -0
  20. data/lib/saml/complex_types/status_response_type.rb +29 -0
  21. data/lib/saml/config.rb +49 -0
  22. data/lib/saml/elements/attribute.rb +24 -0
  23. data/lib/saml/elements/attribute_statement.rb +26 -0
  24. data/lib/saml/elements/audience_restriction.rb +12 -0
  25. data/lib/saml/elements/authn_context.rb +13 -0
  26. data/lib/saml/elements/authn_statement.rb +25 -0
  27. data/lib/saml/elements/conditions.rb +24 -0
  28. data/lib/saml/elements/contact_person.rb +33 -0
  29. data/lib/saml/elements/entities_descriptor.rb +27 -0
  30. data/lib/saml/elements/entity_descriptor.rb +37 -0
  31. data/lib/saml/elements/idp_sso_descriptor.rb +23 -0
  32. data/lib/saml/elements/key_descriptor.rb +34 -0
  33. data/lib/saml/elements/key_descriptor/key_info.rb +30 -0
  34. data/lib/saml/elements/key_descriptor/key_info/x509_data.rb +34 -0
  35. data/lib/saml/elements/name_id.rb +14 -0
  36. data/lib/saml/elements/organization.rb +16 -0
  37. data/lib/saml/elements/requested_authn_context.rb +28 -0
  38. data/lib/saml/elements/signature.rb +33 -0
  39. data/lib/saml/elements/signature/canonicalization_method.rb +19 -0
  40. data/lib/saml/elements/signature/digest_method.rb +19 -0
  41. data/lib/saml/elements/signature/inclusive_namespaces.rb +20 -0
  42. data/lib/saml/elements/signature/key_info.rb +14 -0
  43. data/lib/saml/elements/signature/reference.rb +23 -0
  44. data/lib/saml/elements/signature/signature_method.rb +19 -0
  45. data/lib/saml/elements/signature/signed_info.rb +24 -0
  46. data/lib/saml/elements/signature/transform.rb +19 -0
  47. data/lib/saml/elements/signature/transforms.rb +21 -0
  48. data/lib/saml/elements/sp_sso_descriptor.rb +27 -0
  49. data/lib/saml/elements/status.rb +15 -0
  50. data/lib/saml/elements/status_code.rb +42 -0
  51. data/lib/saml/elements/sub_status_code.rb +14 -0
  52. data/lib/saml/elements/subject.rb +38 -0
  53. data/lib/saml/elements/subject_confirmation.rb +30 -0
  54. data/lib/saml/elements/subject_confirmation_data.rb +23 -0
  55. data/lib/saml/elements/subject_locality.rb +12 -0
  56. data/lib/saml/encoding.rb +35 -0
  57. data/lib/saml/logout_request.rb +10 -0
  58. data/lib/saml/logout_response.rb +11 -0
  59. data/lib/saml/provider.rb +85 -0
  60. data/lib/saml/provider_stores/file.rb +33 -0
  61. data/lib/saml/response.rb +21 -0
  62. data/lib/saml/util.rb +51 -0
  63. data/lib/saml/version.rb +3 -0
  64. data/lib/saml/xml_helpers.rb +34 -0
  65. data/lib/tasks/saml_tasks.rake +4 -0
  66. metadata +195 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NzdjMGJhZTcxYTc2MTUxMmNlMTdhNzEzNDlmOGI4ODEyZDQxMTBhNg==
5
+ data.tar.gz: !binary |-
6
+ NTY3YjkxMDI3OTQyNjUyYTViZDc5MTAxZTBjYzM4ZjllOGVkYzM0Mg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NDcwYjA1YTZmZDcyZGVhZmVmNDlhMjhmMWQ1ZDRiNGIzNzU0YzIzMTE2Y2U5
10
+ ZmUxYmUyYTBkNGVkN2ZhMTZlYjNjZjgzNjE2ZmZmOTIwMzIyNzA0MWQxMjdh
11
+ M2FkY2NjODEyYjdkMTIwNzY5NzVmYmI3MmJhZTIyMzdhZTM2MTE=
12
+ data.tar.gz: !binary |-
13
+ ODdjOTk5ZjJhZWZjMmFiN2M3NWFlYzA4N2Y3Njk2OWUxYTk3YzFmMGZjNTZj
14
+ Y2YwYzcwYzI2YWM0YzNhZjc4NzE4Y2UzYTRjZjBhMzZmNjQ4NjgzNWY4NDU2
15
+ Y2ZjODYxNWE4YTBkMDhmNjY5ZDJjZjQ1NGIzN2E2NzM4ZDJhNWY=
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Digidentity
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,91 @@
1
+ {<img src="https://travis-ci.org/digidentity/libsaml.png?branch=master" alt="Build Status" />}[https://travis-ci.org/digidentity/libsaml]
2
+ = libsaml
3
+ Libsaml is a Ruby gem to easily create SAML 2.0 messages. This gem was written because other SAML gems were missing functionality such as XML signing.
4
+
5
+ Libsaml's features include:
6
+ - Bindings: HTTP-Post, HTTP-Redirect, HTTP-Artifact, SOAP
7
+ - XML signing and verification
8
+ - Pluggable backend for providers (FileStore backend included)
9
+
10
+ Copyright Digidentity BV, released under the MIT license. This gem was written by Benoist Claassen.
11
+
12
+ = Installation
13
+
14
+ Place in your Gemfile:
15
+ gem 'libsaml', require: 'saml'
16
+
17
+ = Usage
18
+ Below follows how to configure the SAML gem in a service provider.
19
+
20
+ Store the private key in:
21
+ config/ssl/key.pem
22
+
23
+ Store the public key of the identity provider in:
24
+ config/ssl/trust-federate.cert
25
+
26
+ Add the Identity Provider web container configuration file to config/metadata/service_provider.xml.
27
+ This contains an encoded version of the public key, generate this in the ruby console by typing:
28
+ irb
29
+ require 'openssl'
30
+ require 'base64'
31
+ pem = File.open("config/ssl/trust-federate.cert").read
32
+ cert = OpenSSL::X509::Certificate.new(pem)
33
+ output = Base64.encode64(cert.to_der).gsub("\n", "")
34
+
35
+ Add the Service Provider configuration file to config/metadata/service_provider.xml:
36
+ <?xml version="1.0" encoding="UTF-8"?>
37
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_052c51476c9560a429e1171e8c9528b96b69fb57" entityID="my:very:original:entityid">
38
+ <md:SPSSODescriptor>
39
+ <md:KeyDescriptor use="signing">
40
+ <ds:KeyInfo>
41
+ <ds:X509Data>
42
+ <ds:X509Certificate>SAME_KEY_AS_GENERATED_IN_THE_CONSOLE_BEFORE</ds:X509Certificate>
43
+ </ds:X509Data>
44
+ </ds:KeyInfo>
45
+ </md:KeyDescriptor>
46
+ <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Post" index="0" Location="http://localhost:3000/saml/receive_response" isDefault="true"/>
47
+ </md:SPSSODescriptor>
48
+ </md:EntityDescriptor>
49
+
50
+ Set up an intializer in config/initializers/saml_config.rb:
51
+ Saml.setup do |config|
52
+ config.entity_id = "my:very:original:entityid"
53
+ config.provider_store = Saml::ProviderStore::File.new("config/metadata", "config/ssl/key.pem")
54
+ # config.provider_store = SamlProvider
55
+ end
56
+
57
+ By default this will use a SamlProvider model that uses the filestore, if you want a database driven model comment out the #provider_store function in the initializer and make a model that defines #find_by_entity_id:
58
+ class SamlProvider < ActiveRecord::Base
59
+ include Saml::Provider
60
+
61
+ def self.find_by_entity_id(entity_id)
62
+ where(entity_id: entity_id).first!
63
+ end
64
+ end
65
+
66
+
67
+ Now you can make a SAML controller in app/controllers/saml_controller.rb:
68
+ class SamlController < ApplicationController
69
+ def request_authentication
70
+ provider = Saml.provider("my:very:original:entityid")
71
+ destination = provider.single_sign_on_service_url(Saml::ProtocolBindings::HTTP_POST)
72
+
73
+ authn_request = Saml::AuthnRequest.new(:destination => destination)
74
+
75
+ @saml_attributes = Saml::Bindings::HTTPPost.create_form_attributes(authn_request)
76
+
77
+ render text: @saml_attributes.to_yaml
78
+ end
79
+
80
+ def receive_response
81
+ end
82
+ end
83
+
84
+ Don't forget to define the routes in config/routes.rb:
85
+ get "/saml/request_authentication" => "saml#request_authentication"
86
+ get "/saml/receive_response" => "saml#receive_response"
87
+
88
+ = Contributing
89
+ - Fork the project
90
+ - Contribute your changes. Please make sure your changes are properly documented and covered by tests.
91
+ - Send a pull request
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Saml'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ require "rspec/core/rake_task"
24
+
25
+ RSpec::Core::RakeTask.new(:core) do |spec|
26
+ spec.rspec_opts = ['--backtrace']
27
+ end
28
+
29
+ task :default => [:core]
30
+
31
+
32
+ Bundler::GemHelper.install_tasks
33
+
data/lib/saml.rb ADDED
@@ -0,0 +1,142 @@
1
+ require 'active_support/all'
2
+ require 'active_model'
3
+ require 'saml/base'
4
+ require 'saml/xml_helpers'
5
+ require 'saml/encoding'
6
+ require 'saml/util'
7
+ require 'xmldsig'
8
+ require 'httpi'
9
+
10
+ module Saml
11
+ MD_NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:metadata'
12
+ SAML_NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion'
13
+ SAMLP_NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:protocol'
14
+ XML_DSIG_NAMESPACE = 'http://www.w3.org/2000/09/xmldsig#'
15
+ SAML_VERSION = '2.0'
16
+
17
+ module Errors
18
+ class SamlError < StandardError;
19
+ end
20
+
21
+ class SignatureInvalid < SamlError;
22
+ end
23
+ class InvalidProvider < SamlError;
24
+ end
25
+ class UnparseableMessage < SamlError;
26
+ end
27
+ end
28
+
29
+ module TopLevelCodes
30
+ SUCCESS = 'urn:oasis:names:tc:SAML:2.0:status:Success'
31
+ REQUESTER = 'urn:oasis:names:tc:SAML:2.0:status:Requester'
32
+ RESPONDER = 'urn:oasis:names:tc:SAML:2.0:status:Responder'
33
+ VERSION_MISMATCH = 'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch'
34
+
35
+ ALL = [SUCCESS, REQUESTER, RESPONDER, VERSION_MISMATCH]
36
+ end
37
+
38
+ module SubStatusCodes
39
+ AUTHN_FAILED = 'urn:oasis:names:tc:SAML:2.0:status:AuthnFailed'
40
+ NO_AUTHN_CONTEXT = 'urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext'
41
+ PARTIAL_LOGOUT = 'urn:oasis:names:tc:SAML:2.0:status:PartialLogout'
42
+ REQUEST_DENIED = 'urn:oasis:names:tc:SAML:2.0:status:RequestDenied'
43
+
44
+ ALL = [AUTHN_FAILED, NO_AUTHN_CONTEXT, PARTIAL_LOGOUT, REQUEST_DENIED]
45
+ end
46
+
47
+ module Bindings
48
+ require 'saml/bindings/http_artifact'
49
+ require 'saml/bindings/http_redirect'
50
+ require 'saml/bindings/http_post'
51
+ require 'saml/bindings/soap'
52
+ end
53
+
54
+ module ClassRefs
55
+ PASSWORD_PROTECTED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
56
+ MOBILE_TWO_FACTOR_CONTRACT = 'urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract'
57
+ MOBILE_SMARTCARD_PKI = 'urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI'
58
+
59
+ ALL_CLASS_REFS = [PASSWORD_PROTECTED,
60
+ MOBILE_TWO_FACTOR_CONTRACT,
61
+ MOBILE_SMARTCARD_PKI]
62
+ ORDERED_CLASS_REFS = ALL_CLASS_REFS
63
+ end
64
+
65
+ module ComplexTypes
66
+ require 'saml/complex_types/request_abstract_type'
67
+ require 'saml/complex_types/status_response_type'
68
+ require 'saml/complex_types/endpoint_type'
69
+ require 'saml/complex_types/indexed_endpoint_type'
70
+ require 'saml/complex_types/sso_descriptor_type'
71
+ end
72
+
73
+ module Elements
74
+ require 'saml/elements/signature'
75
+ require 'saml/elements/subject_locality'
76
+ require 'saml/elements/authn_context'
77
+ require 'saml/elements/audience_restriction'
78
+ require 'saml/elements/sub_status_code'
79
+ require 'saml/elements/status_code'
80
+ require 'saml/elements/status'
81
+ require 'saml/elements/subject_confirmation_data'
82
+ require 'saml/elements/subject_confirmation'
83
+ require 'saml/elements/attribute'
84
+ require 'saml/elements/attribute_statement'
85
+ require 'saml/elements/name_id'
86
+ require 'saml/elements/subject'
87
+ require 'saml/elements/conditions'
88
+ require 'saml/elements/authn_statement'
89
+ require 'saml/elements/requested_authn_context'
90
+ require 'saml/elements/key_descriptor'
91
+ require 'saml/elements/organization'
92
+ require 'saml/elements/contact_person'
93
+ require 'saml/elements/idp_sso_descriptor'
94
+ require 'saml/elements/sp_sso_descriptor'
95
+ require 'saml/elements/entity_descriptor'
96
+ require 'saml/elements/entities_descriptor'
97
+ end
98
+
99
+ require 'saml/assertion'
100
+ require 'saml/authn_request'
101
+ require 'saml/artifact'
102
+ require 'saml/response'
103
+ require 'saml/artifact_resolve'
104
+ require 'saml/artifact_response'
105
+ require 'saml/logout_request'
106
+ require 'saml/logout_response'
107
+ require 'saml/provider'
108
+
109
+ module ProviderStores
110
+ require 'saml/provider_stores/file'
111
+ end
112
+
113
+ module ProtocolBinding
114
+ HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'
115
+ HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
116
+ HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
117
+ SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP'
118
+ end
119
+
120
+ def self.setup
121
+ yield Saml::Config
122
+ end
123
+
124
+ def self.generate_id
125
+ "_#{::SecureRandom.hex(20)}"
126
+ end
127
+
128
+ def self.provider(entity_id)
129
+ Saml::Config.provider_store.find_by_entity_id(entity_id) || raise(Saml::Errors::InvalidProvider.new)
130
+ end
131
+
132
+ def self.parse_message(message, type)
133
+ if %w(authn_request response logout_request logout_response artifact_resolve artifact_response).include?(type.to_s)
134
+ klass = "Saml::#{type.to_s.camelize}".constantize
135
+ klass.parse(message, single: true)
136
+ else
137
+ nil
138
+ end
139
+ end
140
+ end
141
+
142
+ require 'saml/config'
@@ -0,0 +1,51 @@
1
+ module Saml
2
+ class Artifact
3
+ include ::HappyMapper
4
+
5
+ TYPE_CODE = "\000\004"
6
+ END_POINT_INDEX = "\000\000"
7
+
8
+ tag "Artifact"
9
+ namespace 'samlp'
10
+
11
+ content :artifact, String
12
+
13
+ def initialize(artifact = nil)
14
+ if artifact
15
+ @artifact = artifact
16
+ else
17
+ source_id = ::Digest::SHA1.digest(Saml::Config.entity_id)
18
+ message_handle = ::SecureRandom.random_bytes(20)
19
+ @type_code = TYPE_CODE
20
+ @endpoint_index = END_POINT_INDEX
21
+ @artifact = Saml::Encoding.encode_64 [@type_code, @endpoint_index, source_id, message_handle].join
22
+ end
23
+ end
24
+
25
+ def type_code
26
+ decoded_value[0, 2]
27
+ end
28
+
29
+ def endpoint_index
30
+ decoded_value[2, 2]
31
+ end
32
+
33
+ def source_id
34
+ decoded_value[4, 20]
35
+ end
36
+
37
+ def message_handle
38
+ decoded_value[24, 20]
39
+ end
40
+
41
+ def to_s
42
+ artifact
43
+ end
44
+
45
+ private
46
+
47
+ def decoded_value
48
+ ::Base64.decode64(artifact)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ module Saml
2
+ class ArtifactResolve
3
+ include Saml::ComplexTypes::RequestAbstractType
4
+
5
+ tag "ArtifactResolve"
6
+ has_one :artifact, Saml::Artifact
7
+
8
+ validates :artifact, :presence => true
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Saml
2
+ class ArtifactResponse
3
+ include Saml::ComplexTypes::StatusResponseType
4
+
5
+ tag "ArtifactResponse"
6
+
7
+ has_one :response, Saml::Response
8
+ end
9
+ end
@@ -0,0 +1,67 @@
1
+ module Saml
2
+ class Assertion
3
+ include Saml::Base
4
+ include Saml::XMLHelpers
5
+
6
+ register_namespace 'samlp', Saml::SAMLP_NAMESPACE
7
+ register_namespace 'saml', Saml::SAML_NAMESPACE
8
+
9
+ tag "Assertion"
10
+ namespace 'saml'
11
+
12
+ attribute :_id, String, :tag => 'ID'
13
+ attribute :version, String, :tag => "Version"
14
+ attribute :issue_instant, Time, :tag => "IssueInstant", :on_save => lambda { |val| val.utc.xmlschema }
15
+
16
+ element :issuer, String, :namespace => 'saml', :tag => "Issuer"
17
+
18
+ has_one :signature, Saml::Elements::Signature
19
+ has_one :subject, Saml::Elements::Subject
20
+ has_one :conditions, Saml::Elements::Conditions
21
+ has_many :authn_statement, Saml::Elements::AuthnStatement
22
+ has_one :attribute_statement, Saml::Elements::AttributeStatement
23
+
24
+ validates :_id, :version, :issue_instant, :issuer, :presence => true
25
+
26
+ validates :version, inclusion: %w(2.0)
27
+ validate :check_issue_instant, :if => "issue_instant.present?"
28
+
29
+ def initialize(*args)
30
+ options = args.extract_options!
31
+ @subject = Saml::Elements::Subject.new(:name_id => options.delete(:name_id),
32
+ :name_id_format => options.delete(:name_id_format),
33
+ :recipient => options.delete(:recipient),
34
+ :in_response_to => options.delete(:in_response_to))
35
+ @conditions = Saml::Elements::Conditions.new(:audience => options.delete(:audience))
36
+ @authn_statement = Saml::Elements::AuthnStatement.new(:authn_instant => Time.now,
37
+ :address => options.delete(:address),
38
+ :authn_context_class_ref => options.delete(:authn_context_class_ref),
39
+ :session_index => options.delete(:session_index))
40
+ super(*(args << options))
41
+ @_id ||= Saml.generate_id
42
+ @issue_instant ||= Time.now
43
+ @issuer ||= Saml::Config.entity_id
44
+ @version ||= Saml::SAML_VERSION
45
+ end
46
+
47
+ def add_attribute(key, value)
48
+ self.attribute_statement ||= Saml::Elements::AttributeStatement.new
49
+ self.attribute_statement.attribute ||= []
50
+ self.attribute_statement.attribute << Saml::Elements::Attribute.new(name: key, attribute_value: value)
51
+ end
52
+
53
+ def fetch_attribute(key)
54
+ return unless self.attribute_statement
55
+ return unless self.attribute_statement.attribute
56
+ attribute_statement.fetch_attribute(key)
57
+ end
58
+
59
+ private
60
+
61
+ def check_issue_instant
62
+ errors.add(:issue_instant, :too_old) if issue_instant < Time.now - Saml::Config.max_issue_instant_offset.minutes
63
+ errors.add(:issue_instant, :too_new) if issue_instant > Time.now + Saml::Config.max_issue_instant_offset.minutes
64
+ end
65
+
66
+ end
67
+ end