saml-kit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +39 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/exe/saml-kit-decode-http-redirect +7 -0
  12. data/lib/saml/kit.rb +60 -0
  13. data/lib/saml/kit/authentication_request.rb +78 -0
  14. data/lib/saml/kit/binding.rb +40 -0
  15. data/lib/saml/kit/configuration.rb +36 -0
  16. data/lib/saml/kit/default_registry.rb +49 -0
  17. data/lib/saml/kit/document.rb +96 -0
  18. data/lib/saml/kit/fingerprint.rb +40 -0
  19. data/lib/saml/kit/http_post_binding.rb +27 -0
  20. data/lib/saml/kit/http_redirect_binding.rb +58 -0
  21. data/lib/saml/kit/identity_provider_metadata.rb +122 -0
  22. data/lib/saml/kit/invalid_document.rb +13 -0
  23. data/lib/saml/kit/locales/en.yml +25 -0
  24. data/lib/saml/kit/logout_request.rb +78 -0
  25. data/lib/saml/kit/logout_response.rb +63 -0
  26. data/lib/saml/kit/metadata.rb +171 -0
  27. data/lib/saml/kit/namespaces.rb +47 -0
  28. data/lib/saml/kit/requestable.rb +14 -0
  29. data/lib/saml/kit/respondable.rb +35 -0
  30. data/lib/saml/kit/response.rb +197 -0
  31. data/lib/saml/kit/self_signed_certificate.rb +30 -0
  32. data/lib/saml/kit/serializable.rb +31 -0
  33. data/lib/saml/kit/service_provider_metadata.rb +99 -0
  34. data/lib/saml/kit/signature.rb +75 -0
  35. data/lib/saml/kit/trustable.rb +62 -0
  36. data/lib/saml/kit/url_builder.rb +38 -0
  37. data/lib/saml/kit/version.rb +5 -0
  38. data/lib/saml/kit/xml.rb +57 -0
  39. data/lib/saml/kit/xsd/MetadataExchange.xsd +95 -0
  40. data/lib/saml/kit/xsd/oasis-200401-wss-wssecurity-secext-1.0.xsd +196 -0
  41. data/lib/saml/kit/xsd/oasis-200401-wss-wssecurity-utility-1.0.xsd +95 -0
  42. data/lib/saml/kit/xsd/saml-schema-assertion-2.0.xsd +283 -0
  43. data/lib/saml/kit/xsd/saml-schema-authn-context-2.0.xsd +23 -0
  44. data/lib/saml/kit/xsd/saml-schema-authn-context-types-2.0.xsd +821 -0
  45. data/lib/saml/kit/xsd/saml-schema-metadata-2.0.xsd +335 -0
  46. data/lib/saml/kit/xsd/saml-schema-protocol-2.0.xsd +302 -0
  47. data/lib/saml/kit/xsd/sstc-metadata-attr.xsd +35 -0
  48. data/lib/saml/kit/xsd/sstc-saml-attribute-ext.xsd +25 -0
  49. data/lib/saml/kit/xsd/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  50. data/lib/saml/kit/xsd/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  51. data/lib/saml/kit/xsd/ws-addr.xsd +120 -0
  52. data/lib/saml/kit/xsd/ws-authorization.xsd +145 -0
  53. data/lib/saml/kit/xsd/ws-federation.xsd +471 -0
  54. data/lib/saml/kit/xsd/ws-securitypolicy-1.2.xsd +900 -0
  55. data/lib/saml/kit/xsd/xenc-schema.xsd +136 -0
  56. data/lib/saml/kit/xsd/xml.xsd +287 -0
  57. data/lib/saml/kit/xsd/xmldsig-core-schema.xsd +309 -0
  58. data/lib/saml/kit/xsd_validatable.rb +19 -0
  59. data/saml-kit.gemspec +35 -0
  60. metadata +243 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0515744b0635a50a555fd60c1f85bce1852d0ce2
4
+ data.tar.gz: 8326c0666b2734045c89b8d7454677037b3af01f
5
+ SHA512:
6
+ metadata.gz: 6e1648fdcab73f9b17a64632d884ab12ceb7ef92e1df7d6dc0e71371fbc74fd693ae6280373efe83ba80916cfe04dc0fb4357bd4b591c62885aa5c29c1cd06a4
7
+ data.tar.gz: c07432a96634cf785e6a36bef6e690d098863ebc24c311872c021ffc72fa070ff55b69d3b143527e41fdf26bae3696a286d7dff90ef101f48dff6c89db3ac149
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.2
5
+ before_install: gem install bundler -v 1.15.4
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in saml-kit.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 mo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # Saml::Kit
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/saml/kit`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'saml-kit'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install saml-kit
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/saml-kit.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "saml/kit"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'saml/kit'
3
+
4
+ saml = STDIN.read
5
+ binding = Saml::Kit::HttpRedirectBinding.new(location: '')
6
+ xml = binding.deserialize('SAMLRequest' => saml).to_xml
7
+ puts Nokogiri::XML(xml).to_xml(indent: 2)
@@ -0,0 +1,60 @@
1
+ require "saml/kit/version"
2
+
3
+ require "active_model"
4
+ require "active_support/core_ext/date/calculations"
5
+ require "active_support/core_ext/hash/conversions"
6
+ require "active_support/core_ext/numeric/time"
7
+ require "active_support/duration"
8
+ require "builder"
9
+ require "logger"
10
+ require "net/http"
11
+ require "nokogiri"
12
+ require "securerandom"
13
+ require "xmldsig"
14
+
15
+ require "saml/kit/namespaces"
16
+ require "saml/kit/serializable"
17
+ require "saml/kit/xsd_validatable"
18
+ require "saml/kit/respondable"
19
+ require "saml/kit/requestable"
20
+ require "saml/kit/trustable"
21
+ require "saml/kit/document"
22
+
23
+ require "saml/kit/authentication_request"
24
+ require "saml/kit/binding"
25
+ require "saml/kit/configuration"
26
+ require "saml/kit/default_registry"
27
+ require "saml/kit/fingerprint"
28
+ require "saml/kit/logout_response"
29
+ require "saml/kit/logout_request"
30
+ require "saml/kit/http_post_binding"
31
+ require "saml/kit/http_redirect_binding"
32
+ require "saml/kit/metadata"
33
+ require "saml/kit/response"
34
+ require "saml/kit/identity_provider_metadata"
35
+ require "saml/kit/invalid_document"
36
+ require "saml/kit/self_signed_certificate"
37
+ require "saml/kit/service_provider_metadata"
38
+ require "saml/kit/signature"
39
+ require "saml/kit/url_builder"
40
+ require "saml/kit/xml"
41
+
42
+ I18n.load_path += Dir[File.expand_path("kit/locales/*.yml", File.dirname(__FILE__))]
43
+
44
+ module Saml
45
+ module Kit
46
+ class << self
47
+ def configuration
48
+ @config ||= Saml::Kit::Configuration.new
49
+ end
50
+
51
+ def configure
52
+ yield configuration
53
+ end
54
+
55
+ def logger
56
+ configuration.logger
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,78 @@
1
+ module Saml
2
+ module Kit
3
+ class AuthenticationRequest < Document
4
+ include Requestable
5
+ validates_presence_of :acs_url, if: :expected_type?
6
+
7
+ def initialize(xml)
8
+ super(xml, name: "AuthnRequest")
9
+ end
10
+
11
+ def acs_url
12
+ #if signed? && trusted?
13
+ to_h[name]['AssertionConsumerServiceURL'] || registered_acs_url(binding: :post)
14
+ #else
15
+ #registered_acs_url
16
+ #end
17
+ end
18
+
19
+ def name_id_format
20
+ to_h[name]['NameIDPolicy']['Format']
21
+ end
22
+
23
+ def response_for(user)
24
+ Response::Builder.new(user, self)
25
+ end
26
+
27
+ private
28
+
29
+ def registered_acs_url(binding:)
30
+ return if provider.nil?
31
+ provider.assertion_consumer_service_for(binding: binding).try(:location)
32
+ end
33
+
34
+ class Builder
35
+ attr_accessor :id, :now, :issuer, :acs_url, :name_id_format, :sign, :destination
36
+ attr_accessor :version
37
+
38
+ def initialize(configuration: Saml::Kit.configuration, sign: true)
39
+ @id = SecureRandom.uuid
40
+ @issuer = configuration.issuer
41
+ @name_id_format = Namespaces::PERSISTENT
42
+ @now = Time.now.utc
43
+ @version = "2.0"
44
+ @sign = sign
45
+ end
46
+
47
+ def to_xml
48
+ Signature.sign(id, sign: sign) do |xml, signature|
49
+ xml.tag!('samlp:AuthnRequest', request_options) do
50
+ xml.tag!('saml:Issuer', issuer)
51
+ signature.template(xml)
52
+ xml.tag!('samlp:NameIDPolicy', Format: name_id_format)
53
+ end
54
+ end
55
+ end
56
+
57
+ def build
58
+ AuthenticationRequest.new(to_xml)
59
+ end
60
+
61
+ private
62
+
63
+ def request_options
64
+ options = {
65
+ "xmlns:samlp" => Namespaces::PROTOCOL,
66
+ "xmlns:saml" => Namespaces::ASSERTION,
67
+ ID: "_#{id}",
68
+ Version: version,
69
+ IssueInstant: now.utc.iso8601,
70
+ Destination: destination,
71
+ }
72
+ options[:AssertionConsumerServiceURL] = acs_url if acs_url.present?
73
+ options
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,40 @@
1
+ module Saml
2
+ module Kit
3
+ class Binding
4
+ attr_reader :binding, :location
5
+
6
+ def initialize(binding:, location:)
7
+ @binding = binding
8
+ @location = location
9
+ end
10
+
11
+ def binding?(other)
12
+ binding == other
13
+ end
14
+
15
+ def serialize(builder, relay_state: nil)
16
+ []
17
+ end
18
+
19
+ def deserialize(params)
20
+ raise ArgumentError.new("Unsupported binding")
21
+ end
22
+
23
+ def to_h
24
+ { binding: binding, location: location }
25
+ end
26
+
27
+ protected
28
+
29
+ def saml_param_from(params)
30
+ if params['SAMLRequest'].present?
31
+ params['SAMLRequest']
32
+ elsif params['SAMLResponse'].present?
33
+ params['SAMLResponse']
34
+ else
35
+ raise ArgumentError.new("SAMLRequest or SAMLResponse parameter is required.")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ module Saml
2
+ module Kit
3
+ class Configuration
4
+ BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
5
+ END_CERT=/-----END CERTIFICATE-----/
6
+
7
+ attr_accessor :issuer
8
+ attr_accessor :signature_method, :digest_method
9
+ attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
10
+ attr_accessor :registry, :session_timeout
11
+ attr_accessor :logger
12
+
13
+ def initialize
14
+ @signature_method = :SHA256
15
+ @digest_method = :SHA256
16
+ @signing_private_key_password = SecureRandom.uuid
17
+ @signing_certificate_pem, @signing_private_key_pem = SelfSignedCertificate.new(@signing_private_key_password).create
18
+ @registry = DefaultRegistry.new
19
+ @session_timeout = 3.hours
20
+ @logger = Logger.new(STDOUT)
21
+ end
22
+
23
+ def stripped_signing_certificate
24
+ signing_certificate_pem.to_s.gsub(BEGIN_CERT, '').gsub(END_CERT, '').gsub(/\n/, '')
25
+ end
26
+
27
+ def signing_x509
28
+ OpenSSL::X509::Certificate.new(signing_certificate_pem)
29
+ end
30
+
31
+ def signing_private_key
32
+ OpenSSL::PKey::RSA.new(signing_private_key_pem, signing_private_key_password)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ module Saml
2
+ module Kit
3
+ class DefaultRegistry
4
+ def initialize(items = {})
5
+ @items = items
6
+ end
7
+
8
+ def register(metadata)
9
+ @items[metadata.entity_id] = metadata
10
+ end
11
+
12
+ def register_url(url, verify_ssl: true)
13
+ content = HttpApi.new(url, verify_ssl: verify_ssl).get
14
+ register(Saml::Kit::Metadata.from(content))
15
+ end
16
+
17
+ def metadata_for(entity_id)
18
+ @items[entity_id]
19
+ end
20
+
21
+ class HttpApi
22
+ attr_reader :uri, :verify_ssl
23
+
24
+ def initialize(url, verify_ssl: true)
25
+ @uri = URI.parse(url)
26
+ @verify_ssl = verify_ssl
27
+ end
28
+
29
+ def get
30
+ execute(Net::HTTP::Get.new(uri.request_uri)).body
31
+ end
32
+
33
+ def execute(request)
34
+ http.request(request)
35
+ end
36
+
37
+ private
38
+
39
+ def http
40
+ http = Net::HTTP.new(uri.host, uri.port)
41
+ http.read_timeout = 30
42
+ http.use_ssl = uri.is_a?(URI::HTTPS)
43
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless verify_ssl
44
+ http
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,96 @@
1
+ module Saml
2
+ module Kit
3
+ class Document
4
+ PROTOCOL_XSD = File.expand_path("./xsd/saml-schema-protocol-2.0.xsd", File.dirname(__FILE__)).freeze
5
+ include XsdValidatable
6
+ include ActiveModel::Validations
7
+ include Trustable
8
+ validates_presence_of :content
9
+ validates_presence_of :id
10
+ validate :must_match_xsd
11
+ validate :must_be_expected_type
12
+ validate :must_be_valid_version
13
+
14
+ attr_reader :content, :name
15
+
16
+ def initialize(xml, name:)
17
+ @content = xml
18
+ @name = name
19
+ @xml_hash = Hash.from_xml(xml) || {}
20
+ end
21
+
22
+ def id
23
+ to_h.fetch(name, {}).fetch('ID', nil)
24
+ end
25
+
26
+ def issuer
27
+ to_h.fetch(name, {}).fetch('Issuer', nil)
28
+ end
29
+
30
+ def version
31
+ to_h.fetch(name, {}).fetch('Version', {})
32
+ end
33
+
34
+ def destination
35
+ to_h.fetch(name, {}).fetch('Destination', nil)
36
+ end
37
+
38
+ def issue_instant
39
+ to_h[name]['IssueInstant']
40
+ end
41
+
42
+ def expected_type?
43
+ return false if to_xml.blank?
44
+ to_h[name].present?
45
+ end
46
+
47
+ def to_h
48
+ @xml_hash
49
+ end
50
+
51
+ def to_xml
52
+ content
53
+ end
54
+
55
+ def to_s
56
+ to_xml
57
+ end
58
+
59
+ class << self
60
+ def to_saml_document(xml)
61
+ hash = Hash.from_xml(xml)
62
+ if hash['Response'].present?
63
+ Response.new(xml)
64
+ elsif hash['LogoutResponse'].present?
65
+ LogoutResponse.new(xml)
66
+ elsif hash['AuthnRequest'].present?
67
+ AuthenticationRequest.new(xml)
68
+ elsif hash['LogoutRequest'].present?
69
+ LogoutRequest.new(xml)
70
+ end
71
+ rescue => error
72
+ Saml::Kit.logger.error(error)
73
+ InvalidDocument.new(xml)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def must_match_xsd
80
+ matches_xsd?(PROTOCOL_XSD)
81
+ end
82
+
83
+ def must_be_expected_type
84
+ return if to_h.nil?
85
+
86
+ errors[:base] << error_message(:invalid) unless expected_type?
87
+ end
88
+
89
+ def must_be_valid_version
90
+ return unless expected_type?
91
+ return if "2.0" == version
92
+ errors[:version] << error_message(:invalid_version)
93
+ end
94
+ end
95
+ end
96
+ end