Giter Club home page Giter Club logo

saml_idp's Introduction

Ruby SAML Identity Provider (IdP)

Forked from https://github.com/lawrencepit/ruby-saml-idp

Gem Version

The ruby SAML Identity Provider library is for implementing the server side of SAML authentication. It allows your application to act as an IdP (Identity Provider) using the SAML v2.0 protocol. It provides a means for managing authentication requests and confirmation responses for SPs (Service Providers).

This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real SAML IDP implementation.

Installation and Usage

Add this to your Gemfile:

    gem 'saml_idp'

Not using rails?

Include SamlIdp::Controller and see the examples that use rails. It should be straightforward for you.

Basically you call decode_request(params[:SAMLRequest]) on an incoming request and then use the value saml_acs_url to determine the source for which you need to authenticate a user. How you authenticate a user is entirely up to you.

Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by posting to saml_acs_url the parameter SAMLResponse with the return value from a call to encode_response(user_email).

Using rails?

Check out our Wiki page for Rails integration Rails Integration guide

Configuration

Signed assertions and Signed Response

By default SAML Assertion will be signed with an algorithm which defined to config.algorithm, because SAML assertions contain secure information used for authentication such as NameID. Besides that, signing assertions could be optional and can be defined with config.signed_assertion option. Setting this configuration flag to false will add raw assertions on the response instead of signed ones. If the response is encrypted the config.signed_assertion will be ignored and all assertions will be signed.

Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed. For that, you can enable it with signed_message: true option for encode_response(user_email, signed_message: true) method. More about SAML spec

Signing algorithm

Following algorithms you can set in your response signing algorithm :sha1 - RSA-SHA1 default value but not recommended to production environment Highly recommended to use one of following algorithm, suit with your computing power. :sha256 - RSA-SHA256 :sha384 - RSA-SHA384 :sha512 - RSA-SHA512

Be sure to load a file like this during your app initialization:

SamlIdp.configure do |config|
  base = "http://example.com"

  config.x509_certificate = <<-CERT
-----BEGIN CERTIFICATE-----
CERTIFICATE DATA
-----END CERTIFICATE-----
CERT

  config.secret_key = <<-CERT
-----BEGIN RSA PRIVATE KEY-----
KEY DATA
-----END RSA PRIVATE KEY-----
CERT

  # config.password = "secret_key_password"
  # config.algorithm = :sha256                                    # Default: sha1 only for development.
  # config.organization_name = "Your Organization"
  # config.organization_url = "http://example.com"
  # config.base_saml_location = "#{base}/saml"
  # config.reference_id_generator                                 # Default: -> { SecureRandom.uuid }
  # config.single_logout_service_post_location = "#{base}/saml/logout"
  # config.single_logout_service_redirect_location = "#{base}/saml/logout"
  # config.attribute_service_location = "#{base}/saml/attributes"
  # config.single_service_post_location = "#{base}/saml/auth"
  # config.session_expiry = 86400                                 # Default: 0 which means never
  # config.signed_assertion = false                               # Default: true which means signed assertions on the SAML Response
  # config.compress = true                                        # Default: false which means the SAML Response is not being compressed
  # config.logger = ::Logger.new($stdout)                         # Default: if in Rails context - Rails.logger, else ->(msg) { puts msg }. Works with either a Ruby Logger or a lambda

  # Principal (e.g. User) is passed in when you `encode_response`
  #
  # config.name_id.formats =
  #   {                         # All 2.0
  #     email_address: -> (principal) { principal.email_address },
  #     transient: -> (principal) { principal.id },
  #     persistent: -> (p) { p.id },
  #   }
  #   OR
  #
  #   {
  #     "1.1" => {
  #       email_address: -> (principal) { principal.email_address },
  #     },
  #     "2.0" => {
  #       transient: -> (principal) { principal.email_address },
  #       persistent: -> (p) { p.id },
  #     },
  #   }

  # If Principal responds to a method called `asserted_attributes`
  # the return value of that method will be used in lieu of the
  # attributes defined here in the global space. This allows for
  # per-user attribute definitions.
  #
  ## EXAMPLE **
  # class User
  #   def asserted_attributes
  #     {
  #       phone: { getter: :phone },
  #       email: {
  #         getter: :email,
  #         name_format: Saml::XML::Namespaces::Formats::NameId::EMAIL_ADDRESS,
  #         name_id_format: Saml::XML::Namespaces::Formats::NameId::EMAIL_ADDRESS
  #       }
  #     }
  #   end
  # end
  #
  # If you have a method called `asserted_attributes` in your Principal class,
  # there is no need to define it here in the config.

  # config.attributes # =>
  #   {
  #     <friendly_name> => {                                                  # required (ex "eduPersonAffiliation")
  #       "name" => <attrname>                                                # required (ex "urn:oid:1.3.6.1.4.1.5923.1.1.1.1")
  #       "name_format" => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", # not required
  #       "getter" => ->(principal) {                                         # not required
  #         principal.get_eduPersonAffiliation                                # If no "getter" defined, will try
  #       }                                                                   # `principal.eduPersonAffiliation`, or no values will
  #    }                                                                      # be output
  #
  ## EXAMPLE ##
  # config.attributes = {
  #   GivenName: {
  #     getter: :first_name,
  #   },
  #   SurName: {
  #     getter: :last_name,
  #   },
  # }
  ## EXAMPLE ##

  # config.technical_contact.company = "Example"
  # config.technical_contact.given_name = "Jonny"
  # config.technical_contact.sur_name = "Support"
  # config.technical_contact.telephone = "55555555555"
  # config.technical_contact.email_address = "[email protected]"

  service_providers = {
    "some-issuer-url.com/saml" => {
      fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
      metadata_url: "http://some-issuer-url.com/saml/metadata",

      # We now validate AssertionConsumerServiceURL will match the MetadataURL set above.
      # *If* it's not going to match your Metadata URL's Host, then set this so we can validate the host using this list
      response_hosts: ["foo.some-issuer-url.com"]
    },
  }

  # `identifier` is the entity_id or issuer of the Service Provider,
  # settings is an IncomingMetadata object which has a to_h method that needs to be persisted
  config.service_provider.metadata_persister = ->(identifier, settings) {
    fname = identifier.to_s.gsub(/\/|:/,"_")
    FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
    File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
      Marshal.dump settings.to_h, f
    end
  }

  # `identifier` is the entity_id or issuer of the Service Provider,
  # `service_provider` is a ServiceProvider object. Based on the `identifier` or the
  # `service_provider` you should return the settings.to_h from above
  config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
    fname = identifier.to_s.gsub(/\/|:/,"_")
    FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
    full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
    if File.file?(full_filename)
      File.open full_filename, "rb" do |f|
        Marshal.load f
      end
    end
  }

  # Find ServiceProvider metadata_url and fingerprint based on our settings
  config.service_provider.finder = ->(issuer_or_entity_id) do
    service_providers[issuer_or_entity_id]
  end
end

Keys and Secrets

To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret. You can find them in SamlIdp::Default. The X.509 certificate is valid until year 2032. Obviously you shouldn't use these if you intend to use this in production environments. In that case, within the controller set the properties x509_certificate and secret_key using a prepend_before_action callback within the current request context or set them globally via the SamlIdp.config.x509_certificate and SamlIdp.config.secret_key properties.

The fingerprint to use, if you use the default X.509 certificate of this gem, is:

  9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D

Fingerprint

The gem provides an helper to generate a fingerprint for a X.509 certificate. The second parameter is optional and default to your configuration SamlIdp.config.algorithm

  SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)

Service Providers

To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the excellent ruby-saml gem.

Author

Jon Phenow, [email protected], jphenow.com, @jphenow

Lawrence Pit, [email protected], lawrencepit.com, @lawrencepit

Copyright

Copyright (c) 2012 Sport Ngin. Portions Copyright (c) 2010 OneLogin, LLC Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)

See LICENSE for details.

saml_idp's People

Contributors

4-eyes avatar aaronchi avatar ccutrer avatar davidfou avatar deshlema avatar drnic avatar ethznn avatar flugsio avatar fursich avatar hamaron avatar ianderse avatar jkamenik avatar johnf avatar johnnaegle avatar jphenow avatar lawrencepit avatar mjobin-mdsol avatar mvastola avatar nzacca avatar olleolleolle avatar otsogbadrakhchinzorig avatar petergoldstein avatar pkarman avatar shiro16 avatar sivagollapalli avatar smsohan avatar taketo1113 avatar tknzk avatar zach-taylor avatar zogoo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

saml_idp's Issues

Feature Request: Multiple IdP configurations

Hi,

I have a need for two IdP configurations that read different principal attributes and encrypt with different x509 certs. Is there a means to do this in the current version of this gem? Might there be a way to extend the current gem to allow for this functionality?

Setting config.entity_id does not set issuer in SAML Response

Setting the config.entity_id correctly sets the entity id in the metadata response. However it does not update the issuer in the SAML response, that is defaulted to {host}/saml/auth

Ex.

config.entity_id = 'http://localhost:3000

Metadata Response:

<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_d6848690-8a51-0133-02e8-14109fe48bcd" entityID="http://localhost:3000">

SAML Response:

<samlp:Response ID="_cff041b0-8a58-0133-02e9-14109fe48bcd" Version="2.0" IssueInstant="2015-12-21T21:36:26Z" Destination="http://localhost:8080/" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_5513e04e119851e5278ffc971f0d03ef5028b930f5" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:4000/saml/auth</Issuer>

I would assume the entityID in the Metadata Response would equal the Issuer in the SAML Response.

No Action Defined for "#{base}/saml/attributes"

The configuration in the README.md has this line:

config.attribute_service_location = "#{base}/saml/attributes"

.. but neither SamlIdp::IdpController nor SamlIdp::Controller seem to define an method that is meant to be mapped to this route.

Can the documentation be provided to offer example logic to handle this action?

Update build matrix

We're building against some very old ruby and rails. let's reduce the footprint to at least what's still commonly supported

Examples? such as a devise example

I wondered if there should be an examples directory that had some different example deployments. It might be "handy" for new implementations? For example. We had an "existing" rails application that uses "devise" for user authentication / management. We just wanted to add the ability for it to be the IdP. It turns out that is super easy, once you have fought it long enough ;)

Devise Example

app/controllers/saml_idp_controller.rb

class SamlIdpController < SamlIdp::IdpController
  before_filter :authenticate_user!, except: [:show]

# override create and make sure to set both "GET" and "POST" requests to /saml/auth to #create
  def create
    if user_signed_in?
      @saml_response = idp_make_saml_response(current_user)
      render :template => "saml_idp/idp/saml_post", :layout => false
      return 
    else
      # it shouldn't be possible to get here, but lets render 403 just in case
      render :status => :forbidden
    end
  end

# NOT USED -- def idp_authenticate(email, password) -- NOT USED

  def idp_make_saml_response(found_user) # not using params intentionally
    encode_response found_user
  end
  private :idp_make_saml_response
end

config/routes.rb

(add the following)

# SAMLv2 IdP
  get '/saml/auth' => 'saml_idp#create'
  post '/saml/auth' => 'saml_idp#create'
  get '/saml/metadata' => 'saml_idp#show'

config/initializers/saml_idp.rb

Add this file per the README. Note that it does require customization.

  • You must set "base" and it should probably be something like base = Rails.configuration.x.saml_idp_base which should be set as config.x.saml_idp_base = https://www.example.com/ in your config/environments/${RAILS_ENV}.rb.
  • The SSL KEY should probably be in secrets.yaml (and not in version control).
  • The config.name_id_formats must be set (watch out, the example has a # before the =)
  • Also, for config.service_provider.persisted_metadata_getter, see Issue #16 just in case that hasn't been fixed yet.

How can we help the maintainers?

several pull requests seems ready.

even one was planned for a release earlier this year.

we are a few people ready to help.

I am thinking @Zogoo as the SAML expert and I can help with release coordination.

can @jphenow find sometime to review open requests and cut a release? or can you give github+rubygems write access to someone ?

thank you

Logout

Can anyone please explain how to implement logging out? There is, what looks like, some code relating to responding to a logout request, but I'm not sure...

License missing from gemspec

RubyGems.org doesn't report a license for your gem. This is because it is not specified in the gemspec of your last release.

via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

Including a license in your gemspec is an easy way for rubygems.org and other tools to check how your gem is licensed. As you can image, scanning your repository for a LICENSE file or parsing the README, and then attempting to identify the license or licenses is much more difficult and more error prone. So, even for projects that already specify a license, including a license in your gemspec is a good practice. See, for example, how rubygems.org uses the gemspec to display the rails gem license.

There is even a License Finder gem to help companies/individuals ensure all gems they use meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough issue that even Bundler now generates gems with a default 'MIT' license.

I hope you'll consider specifying a license in your gemspec. If not, please just close the issue with a nice message. In either case, I'll follow up. Thanks for your time!

Appendix:

If you need help choosing a license (sorry, I haven't checked your readme or looked for a license file), GitHub has created a license picker tool. Code without a license specified defaults to 'All rights reserved'-- denying others all rights to use of the code.
Here's a list of the license names I've found and their frequencies

p.s. In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally looking for download data) and decided to collect license metadata,too, and make issues for gemspecs not specifying a license as a public service :). See the previous link or my blog post about this project for more information.

saml/metadata endpoint generates <script/> tag

Hi

When I go to the saml/metadata endpoint of my identity provider I see the generated metadata, and it starts like this:

<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ID="_01fe34f0-60ff-0136-f794-745c89be5ec5" entityID="http://localhost:3030/saml">
<script/>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
...more xml

why <script /> is being generated? When I try to validate this (ie. on
saml tool)

then it's not being validated correctly. Additionally, when I try to register my identity provider with a service provider that I'm supposed to use (third part app) I can't because of the metadata being not correct (checked that, because I have manually generated metadata based on the saml tool above and was able to register my identity provider)

Has anyone had similar issues?

Service Provider never gets the sign assertion flag from incoming SP metadata

The ServiceProvider object will never get the sign assertions flag from incoming metadata because it is calling from the wrong method signature. sign_assertions vs sign_assertions?

https://github.com/sportngin/saml_idp/blob/master/lib/saml_idp/service_provider.rb#L27
^ Here we can see it checking for a sign_assertions? method here current_metadata.respond_to?(:sign_assertions?) && current_metadata.sign_assertions?

https://github.com/sportngin/saml_idp/blob/master/lib/saml_idp/incoming_metadata.rb#L19
^ Here we can see that incoming_metadata.rb has no sign_assertions? method but instead has a sign_assertions method.

Problem with getting logout to work properly

I'm having difficulties with implementing the logout functionality of your otherwise awesome IDP.

Currently the result of calling the IDP#logout action with SP SLO object results in this error:
undefined method `authn_request?' for nil:NilClass at the method "encode_response":

def idp_make_saml_response(found_user)
encode_response found_user
end

I've tried to put in a debugger to get a look at what's going on, and currently I'm confused how come the before_filter "before_filter :validate_saml_request, only: [:new, :create]" in saml_idp/app/controllers/saml_idp/idp_controller.rb should not be applied for the logout action as well.

As far as I see, the method 'validate_saml_request' is the only one which calls 'decode_request' in saml_idp/app/controllers/saml_idp/idp_controller.rb which results in setting self.saml_request.
And it seems like the method "encode_response" depends on it being set.

Currently my method "idp_logout" is plain empty, but I don't think that should be a problem, should it?

Thank you in advance.

Rails dependency in controller

In controller.rb

    def validate_saml_request(raw_saml_request = params[:SAMLRequest])
      decode_request(raw_saml_request)
      return true if valid_saml_request?
      if Rails::VERSION::MAJOR >= 4
        head :forbidden
      else
         render nothing: true, status: forbidden
      end

this check for Rails::VERSION::MAJOR fails if not using Rails (e.g. I'm pulling include SamlIdp::Controller into a Sinatra project). It also fails because of the render call. Both of these are leakages from Rails; this should probably just return an error/exception and let the upstream render the appropriate response.

deprecate rails 3

shall we remove all rails 3 specific code and conditions such as Rails::VERSION::MAJOR >= 4 and make a release?

4.2 is out of support, I don't see a point supporting rails 3
but I guess if it does not create an overhead to keep it, there is no reason to remove.

opening for discussion

Reference validation failed

I am sure this is something I am doing wrong, but any assistance is appreciated.

I have the following in my initializer:

  config.attributes = {
    displayName: {
      getter: -> (principal) { principal.full_name },
    },
    givenName: {
      getter: :first_name,
    },
    sn: {
      getter: :last_name,
    },
    title: {
      getter: :title,
    },
    mail: {
      getter: :email,
    },
    telephoneNumber: {
      getter: :phone,
    },
  }

... and I am getting "Reference validation failed" in simplesamlphp. if I comment out the displayName section -or- set it to something simple like ":first_name" (although that is useless that way), it works fine.

I also put it through the validator at www.samltool.com and got the same error, so I don't think its simplesamlphp. Something I noticed when I captured the SAML Response was that the attributes got "corrupted"...

    <AttributeStatement>
      <Attribute Name="displayName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="displayName">
        <AttributeValue>Sandy Saunders Saunders</AttributeValue>
      </Attribute>
      <Attribute Name="givenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="givenName">
        <AttributeValue>Sandy Saunders Saunders</AttributeValue>
      </Attribute>
      <Attribute Name="sn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="sn">
        <AttributeValue>Saunders</AttributeValue>
      </Attribute>
      <Attribute Name="title" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="title">
        <AttributeValue>Library User</AttributeValue>
      </Attribute>
      <Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="mail">
        <AttributeValue>[email protected]</AttributeValue>
      </Attribute>
      <Attribute Name="telephoneNumber" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" FriendlyName="telephoneNumber">
        <AttributeValue>3035551212</AttributeValue>
      </Attribute>
    </AttributeStatement>

See how the displayName and givenName both have like FirstName LastName LastName? It does not look like that in the database. I am betting that would be the reason for the "reference validation" issue. I am sure it has something to do with the "getter" for displayName. Note that I also tried setting the getter to :full_name which is just a simple method that combines the :first_name and :last_name values in a string.

~tommy

Adding acs_url for Service Provider in initializer

I've been trying to add the acs_url for the service provider. I currently have the service_providers hash in the initializer like this:

service_providers = {
    "localhost:8000/saml" => {
      fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
      metadata_url: "localhost:8000/saml/metadata"
    },
  }

I keep running into an error where acs_url is returning nil. Is there a place where I should be adding the acs_url?

`undefined method 'persistent' for #<Principal>

Hi, I'm trying to stand this up to do some testing.

Here's my config this section, based on the closed ticket:

  config.name_id.formats # =>
    {                         # All 2.0
      email_address: -> (principal) { principal.email_address },
      transient: -> (principal) { principal.id },
      persistent: -> (principal) { principal.id },
    }

The User (which is my principal) supports id. I can not find a reference to a persistent method in your code outside this configuration.

Login is successful. This gets thrown during this block:

 def idp_make_saml_response(found_user) # not using params intentionally
    # NOTE encryption is optional
    encode_response found_user, encryption: {
        cert: saml_request.service_provider.cert,
        block_encryption: 'aes256-cbc',
        key_transport: 'rsa-oaep-mgf1p'
    }
  end

Does not validate Signature when authenticating via HTTP redirect

When using HTTP-Redirect Binding for SSO, the Signature Algorithm and the Signature are parameters in the request. This gem does not currently support getting those attributes from the request and validating them. Only when the data comes from a HTTP post is the Signature and Algorithm part of the AuthRequest xml. Therefore no signing validation is done when Authentication is happening from an HTTP Redirect.

Example:

Started GET "/saml/auth?SAMLRequest=jZJRb9sgFIXf9ysQ7wSwldhGcaqsUbVI3RY17h76MlFMEiR88QC3278fsRep09aqvHHvubrfObrLq5%2BdRU%2FaB%2BOgxnzGMNKgXGvgWOP75oaU%2BGq1DLKzvVgP8QR3%2BsegQ0RpDoIYGzUePAgngwkCZKeDiErs159vRTZjovcuOuUsnkbeFssQtI%2BJBaPtpsbfi7zNVJVlcp7njB2YKuYZk8Wc80KVRV6ydrGQVXV45Bh9u7jIzi62IQx6CyFKiKnE%2BIKwnLCq4QvBS8GrB4x2f9A%2BGpgMv4X2OImC%2BNQ0O7L7um8wWl9wrx2EodN%2Br%2F2TUfr%2B7rbGpxh7Qal1StqTC1GUrGQUo02Kz4CMI%2Bs%2FomST0XOsVKa48ZS9GN34F6G%2FN8bV%2Fymee6IcRA2R9nY4Ggi0M2Ccl3DU5LyAZIyE5NemrzkCcUCX9AXL5Si%2BpOXbzc5Zo36htbXu%2BdprGXWNox80RjfOdzK%2BjstnfKyYlhxGqRgg9FqZg9Et%2FoBeeXQ10fx9lavf&RelayState=testValidate&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=eIHdZLz5VL0OF6ZkieDWcEDFWQ40yyKavS2jQMj1EplB8hsRbsplOyq%2B8gj5F91UBJIfaz8jKNXULDyeY7iXjX4kIDUqJuX69CgY7GS%2Fb%2FTpsRc7l4YDyhlhzHOH%2B2WSjiMzrofTYhh8zNiXJeXyHHhwvg0EleAJc8Uz2892NXFDWIxXfbY%2FA4cbBqUTz0vvl4q99Kns6v%2BDBHntA52W%2FjgMEed08GGTP2vQewUpT3V3eJb98nHJOqvJzu8qK8YM%2Fv9xGNMc1nvt8UJ8kY8uaLjWVg1ABs0X2mjqDfc5yXHqZQThlSSUBX3N1w%2FvZBzVtYjtkuJVWEpnejazJ8ginw%3D%3D" for 127.0.0.1 at 2016-03-09 09:18:18 -0700
Processing by SamlIdpController#create as HTML
  Parameters: {"SAMLRequest"=>"jZJRb9sgFIXf9ysQ7wSwldhGcaqsUbVI3RY17h76MlFMEiR88QC3278fsRep09aqvHHvubrfObrLq5+dRU/aB+OgxnzGMNKgXGvgWOP75oaU+Gq1DLKzvVgP8QR3+segQ0RpDoIYGzUePAgngwkCZKeDiErs159vRTZjovcuOuUsnkbeFssQtI+JBaPtpsbfi7zNVJVlcp7njB2YKuYZk8Wc80KVRV6ydrGQVXV45Bh9u7jIzi62IQx6CyFKiKnE+IKwnLCq4QvBS8GrB4x2f9A+GpgMv4X2OImC+NQ0O7L7um8wWl9wrx2EodN+r/2TUfr+7rbGpxh7Qal1StqTC1GUrGQUo02Kz4CMI+s/omST0XOsVKa48ZS9GN34F6G/N8bV/ymee6IcRA2R9nY4Ggi0M2Ccl3DU5LyAZIyE5NemrzkCcUCX9AXL5Si+pOXbzc5Zo36htbXu+dprGXWNox80RjfOdzK+jstnfKyYlhxGqRgg9FqZg9Et/oBeeXQ10fx9lavf", "RelayState"=>"testValidate", "SigAlg"=>"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "Signature"=>"eIHdZLz5VL0OF6ZkieDWcEDFWQ40yyKavS2jQMj1EplB8hsRbsplOyq+8gj5F91UBJIfaz8jKNXULDyeY7iXjX4kIDUqJuX69CgY7GS/b/TpsRc7l4YDyhlhzHOH+2WSjiMzrofTYhh8zNiXJeXyHHhwvg0EleAJc8Uz2892NXFDWIxXfbY/A4cbBqUTz0vvl4q99Kns6v+DBHntA52W/jgMEed08GGTP2vQewUpT3V3eJb98nHJOqvJzu8qK8YM/v9xGNMc1nvt8UJ8kY8uaLjWVg1ABs0X2mjqDfc5yXHqZQThlSSUBX3N1w/vZBzVtYjtkuJVWEpnejazJ8ginw=="}

Document.valid_signature? calls incorrect method to check fingerprint

The valid_signature? method in the Document class attempts to call a method from xml_security.rb that does not exist. See https://github.com/sportngin/saml_idp/blob/master/lib/saml_idp.rb#L74.

XMLSecurity::SignedDocument.validate_document(fingerprint, :soft) is not a supported method for the class. I believe what it should call is XMLSecurity::SignedDocument.validate(idp_cert_fingerprint, soft = true) which can be found here https://github.com/sportngin/saml_idp/blob/master/lib/saml_idp/xml_security.rb#L46

Single Log-Out Does Not Appear to Support Mupltile SPs

Single Log-Out was implemented (at least in theory) by #41 by @pkarman, but the purpose of the feature, (at least as I understand it) is that a logout command sent to the IdP results in logouts at any SPs that have been logged into during the login session. (See http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html#5.3.2.SP-Initiated%20Single%20Logout%20with%20Multiple%20SPs|outline)

From looking at the code here though, I'm not quite sure how that's implemented. For instance, the IdP doesn't seem to maintain (or seek to use) a per-session list of the SPs that have been signed into, nor is there any sort of loop in the SamlIdp::IdpController. Is this support missing, or am I missing something?

If the former, could this be considered a feature request?

I understand that the README.md states:

This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real SAML IDP implementation.

.. which implies this implementation isn't feature complete. I'm frankly quite grateful that this gem exists to begin with, but is there any way the README.md could also perhaps list the ways in which this isn't a full implementation? (It would just make it a lot easier for users to work around the incomplete features and perhaps even know exactly what to offer PRs for.)

Thanks a ton,
Mike

how to help getting this project unstuck?

dear owners of this repo (@jphenow),
it seems this repo is somewhat popular, there is a backlog of useful additions and a small community of users who seem to be willing to contribute. It would be great to get it somehow unstuck! Please let us know how we can help to achieve this! For example I would love to donate some time reviewing open PR's, or help out in any other way.

Attributes cause Digest mismatch

I am trying to debug why, but it seems that the response that saml_idp provides after login are not signed correctly.

I am new to SAML, and I have the client working against another IdP which I didn't setup. I am trying to setup a IdP to get an understanding of the process, but also to build an actual directory for my company.

I am not sure how to debug this issue, but this is the client's log. I don't actually see anything useful in the IdP log.

Started GET "/auth/saml" for 127.0.0.1 at 2013-11-27 16:30:31 -0500
(saml) Request phase initiated.
Created AuthnRequest: <samlp:AuthnRequest AssertionConsumerServiceURL='http://saml-client.dev/auth/saml/callback' Destination='http://saml-idp.dev/saml/auth' ID='_113982c0-39d9-0131-99a6-482a14030d65' IssueInstant='2013-11-27T21:30:31Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>waterfall-saml-client</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/></samlp:AuthnRequest>


Started POST "/auth/saml/callback" for 127.0.0.1 at 2013-11-27 16:30:38 -0500
(saml) Callback phase initiated.
(saml) Authentication failure! invalid_ticket: Onelogin::Saml::ValidationError, Digest mismatch

My setup:

  1. Mac OS X (Mavericks)
  2. Pow
  3. IdP full source: https://github.com/WaterfallFMS/saml_idp
  4. Client full source: https://github.com/WaterfallFMS/saml_client

Any help you could give in debugging the issue would be great.

cannot load such file -- xmlenc

I am trying to encrypt my response by calling

encode_response(user, encryption: {
        block_encryption: 'aes256-cbc',
        key_transport: 'rsa-oaep-mgf1p'
})

but I keep getting cannot load such file -- xmlenc. Has anyone else gotten this issue?

undefined method `persistent' for #<SomeClass>

Is there a way to configure what is called on the principal?
There is no method in the Configurator that can set those things. I was misled by the snippet in the readme that there should be one.

SomeClass is the class of the principal that I'm passing to encode_response

IncomingMetadata does't return entity id

Entity ID from SP metadata will help easier to identify SP information.
When dynamically register new SP to DB, it's required to identify that SP with entity id.
Of course we can do following trick, i would like to have inside of IncomingMetadata
`
hash = SamlIdp::IncomingMetadata.new(raw_metadata)

entity_id = hash.document.xpath('//md:EntityDescriptor/@EntityID').first.value

hash[:entity_id] = entity_id

`

I have fixed this issue in this pull request: #109

Errno::ENOENT (No such file or directory @ rb_sysopen - .../cache/saml/metadata/Influitive-AdvocateHub)

Subj, what should be there?

Errno::ENOENT (No such file or directory @ rb_sysopen - /var/www/crm.wegohealth.com/releases/20150218112304/cache/saml/metadata/Influitive-AdvocateHub):
  config/initializers/saml_idp.rb:118:in `initialize'
  config/initializers/saml_idp.rb:118:in `open'
  config/initializers/saml_idp.rb:118:in `block (2 levels) in <top (required)>'
  lib/response_timer.rb:21:in `call'
  lib/core_ext/thread.rb:16:in `call'
  lib/core_ext/thread.rb:16:in `block in initialize'

idp_make_saml_response (default NotImplementedError)

I wondered why this would not be implemented in idp_controller.rb:

https://github.com/sportngin/saml_idp/blob/master/app/controllers/saml_idp/idp_controller.rb#L37-L39

    def idp_make_saml_response(person)
      raise NotImplementedError
    end

When it seems fairly simple and straightforward in README.md:

  def idp_make_saml_response(found_user) # not using params intentionally
    encode_response found_user
  end
  private :idp_make_saml_response

I am sure I must be missing something, but it seems like the code from the README could just be in the class? (although I think I would stick with "person" rather than "found_user") IF the implementation required it to be overridden for whatever reason, they could still do it.

I do understand why idp_authenticate is setup that way. That is a very implementation specific detail that really wouldn't make sense to have a default method for.

~tommy

Ability to use password with key

Our private key file is passworded. In the original ruby_saml_idp you could override the 'sign' method in the controller to deal with this. Would it make sense to make a password config variable so this could be passed to the current 'encoded' method where the key handling is?

Undefined method persistent for User

Please find the code and backtrace below.

This is the controller :


class SamlIdpController < SamlIdp::IdpController
  def idp_authenticate(email, password) # not using params intentionally
    return User.find_by_id(1)
  end
  private :idp_authenticate

  def idp_make_saml_response(found_user) # not using params intentionally
    # NOTE encryption is optional
    encode_response found_user
  end
  private :idp_make_saml_response

  def idp_logout
    user = User.by_email(saml_request.name_id)
    user.logout
  end
  private :idp_logout
end

This is the error backtrace


NoMethodError (undefined method `persistent' for #<User:0x007f8435bd9e88>
Did you mean?  persisted?):
  
app/controllers/saml_idp_controller.rb:9:in `idp_make_saml_response'

Full Backtrace :

activemodel (5.1.5) lib/active_model/attribute_methods.rb:432:in `method_missing'
saml_idp (0.7.2) lib/saml_idp/name_id_formatter.rb:44:in `public_send'
saml_idp (0.7.2) lib/saml_idp/name_id_formatter.rb:44:in `block in build_getter'
saml_idp (0.7.2) lib/saml_idp/assertion_builder.rb:127:in `name_id'
saml_idp (0.7.2) lib/saml_idp/assertion_builder.rb:45:in `block (2 levels) in fresh'
builder (3.2.3) lib/builder/xmlbase.rb:175:in `_nested_structures'
builder (3.2.3) lib/builder/xmlbase.rb:68:in `tag!'
builder (3.2.3) lib/builder/xmlbase.rb:93:in `method_missing'
saml_idp (0.7.2) lib/saml_idp/assertion_builder.rb:44:in `block in fresh'
builder (3.2.3) lib/builder/xmlbase.rb:175:in `_nested_structures'
builder (3.2.3) lib/builder/xmlbase.rb:68:in `tag!'
builder (3.2.3) lib/builder/xmlbase.rb:93:in `method_missing'
saml_idp (0.7.2) lib/saml_idp/assertion_builder.rb:38:in `fresh'
saml_idp (0.7.2) lib/saml_idp/signable.rb:95:in `get_raw'
saml_idp (0.7.2) lib/saml_idp/signable.rb:100:in `noko_raw'
saml_idp (0.7.2) lib/saml_idp/signable.rb:109:in `digest'
saml_idp (0.7.2) lib/saml_idp/signable.rb:84:in `block in get_digest'
saml_idp (0.7.2) lib/saml_idp/signable.rb:56:in `without_signature'
saml_idp (0.7.2) lib/saml_idp/signable.rb:83:in `get_digest'
saml_idp (0.7.2) lib/saml_idp/signable.rb:73:in `signed_info_builder'
saml_idp (0.7.2) lib/saml_idp/signable.rb:68:in `signature'
saml_idp (0.7.2) lib/saml_idp/signable.rb:24:in `sign'
saml_idp (0.7.2) lib/saml_idp/assertion_builder.rb:43:in `block in fresh'
builder (3.2.3) lib/builder/xmlbase.rb:175:in `_nested_structures'
builder (3.2.3) lib/builder/xmlbase.rb:68:in `tag!'
builder (3.2.3) lib/builder/xmlbase.rb:93:in `method_missing'
saml_idp (0.7.2) lib/saml_idp/assertion_builder.rb:38:in `fresh'
saml_idp (0.7.2) lib/saml_idp/signable.rb:18:in `block (2 levels) in signed'
saml_idp (0.7.2) lib/saml_idp/signable.rb:47:in `with_signature'
saml_idp (0.7.2) lib/saml_idp/signable.rb:17:in `block in signed'
saml_idp (0.7.2) lib/saml_idp/signable.rb:29:in `generated_reference_id'
saml_idp (0.7.2) lib/saml_idp/signable.rb:16:in `signed'
saml_idp (0.7.2) lib/saml_idp/saml_response.rb:58:in `signed_assertion'
saml_idp (0.7.2) lib/saml_idp/saml_response.rb:64:in `response_builder'
saml_idp (0.7.2) lib/saml_idp/saml_response.rb:51:in `build'
saml_idp (0.7.2) lib/saml_idp/controller.rb:79:in `encode_authn_response'
saml_idp (0.7.2) lib/saml_idp/controller.rb:94:in `encode_response'
app/controllers/saml_idp_controller.rb:9:in `idp_make_saml_response'
saml_idp (0.7.2) app/controllers/saml_idp/idp_controller.rb:30:in `create'
actionpack (5.1.5) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
actionpack (5.1.5) lib/abstract_controller/base.rb:186:in `process_action'
actionpack (5.1.5) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.1.5) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
activesupport (5.1.5) lib/active_support/callbacks.rb:131:in `run_callbacks'
actionpack (5.1.5) lib/abstract_controller/callbacks.rb:19:in `process_action'
actionpack (5.1.5) lib/action_controller/metal/rescue.rb:20:in `process_action'
actionpack (5.1.5) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
activesupport (5.1.5) lib/active_support/notifications.rb:166:in `block in instrument'
activesupport (5.1.5) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
activesupport (5.1.5) lib/active_support/notifications.rb:166:in `instrument'
actionpack (5.1.5) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (5.1.5) lib/action_controller/metal/params_wrapper.rb:252:in `process_action'
activerecord (5.1.5) lib/active_record/railties/controller_runtime.rb:22:in `process_action'
actionpack (5.1.5) lib/abstract_controller/base.rb:124:in `process'
actionview (5.1.5) lib/action_view/rendering.rb:30:in `process'
actionpack (5.1.5) lib/action_controller/metal.rb:189:in `dispatch'
actionpack (5.1.5) lib/action_controller/metal.rb:253:in `dispatch'
actionpack (5.1.5) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'
actionpack (5.1.5) lib/action_dispatch/routing/route_set.rb:31:in `serve'
actionpack (5.1.5) lib/action_dispatch/journey/router.rb:50:in `block in serve'
actionpack (5.1.5) lib/action_dispatch/journey/router.rb:33:in `each'
actionpack (5.1.5) lib/action_dispatch/journey/router.rb:33:in `serve'
actionpack (5.1.5) lib/action_dispatch/routing/route_set.rb:844:in `call'
warden (1.2.7) lib/warden/manager.rb:36:in `block in call'
warden (1.2.7) lib/warden/manager.rb:35:in `catch'
warden (1.2.7) lib/warden/manager.rb:35:in `call'
rack (2.0.4) lib/rack/etag.rb:25:in `call'
rack (2.0.4) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.4) lib/rack/head.rb:12:in `call'
rack (2.0.4) lib/rack/session/abstract/id.rb:232:in `context'
rack (2.0.4) lib/rack/session/abstract/id.rb:226:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/cookies.rb:613:in `call'
activerecord (5.1.5) lib/active_record/migration.rb:556:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'
activesupport (5.1.5) lib/active_support/callbacks.rb:97:in `run_callbacks'
actionpack (5.1.5) lib/action_dispatch/middleware/callbacks.rb:24:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'
web-console (3.5.1) lib/web_console/middleware.rb:135:in `call_app'
web-console (3.5.1) lib/web_console/middleware.rb:28:in `block in call'
web-console (3.5.1) lib/web_console/middleware.rb:18:in `catch'
web-console (3.5.1) lib/web_console/middleware.rb:18:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.1.5) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.1.5) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.1.5) lib/active_support/tagged_logging.rb:69:in `block in tagged'
activesupport (5.1.5) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.1.5) lib/active_support/tagged_logging.rb:69:in `tagged'
railties (5.1.5) lib/rails/rack/logger.rb:24:in `call'
sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:13:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/request_id.rb:25:in `call'
rack (2.0.4) lib/rack/method_override.rb:22:in `call'
rack (2.0.4) lib/rack/runtime.rb:22:in `call'
activesupport (5.1.5) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.5) lib/action_dispatch/middleware/static.rb:125:in `call'
rack (2.0.4) lib/rack/sendfile.rb:111:in `call'
railties (5.1.5) lib/rails/engine.rb:522:in `call'
puma (3.11.2) lib/puma/configuration.rb:225:in `call'
puma (3.11.2) lib/puma/server.rb:624:in `handle_request'
puma (3.11.2) lib/puma/server.rb:438:in `process_client'
puma (3.11.2) lib/puma/server.rb:302:in `block in run'
puma (3.11.2) lib/puma/thread_pool.rb:120:in `block in spawn_thread'

This is my initializer


SamlIdp.configure do |config|
#   SamlIdp.config.base = "http://localhost:3000/saml/auth"

config.x509_certificate = <<-CERT
-----BEGIN CERTIFICATE-----
MIIDNjCCAh4CCQCKVUybLPj81TANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJJ
TjELMAkGA1UECAwCVFMxDDAKBgNVBAcMA0hZRDESMBAGA1UECgwJUHJpbWVhdXRo
MR8wHQYJKoZIhvcNAQkBFhBoaUBwcmltZWF1dGguY29tMB4XDTE4MDMwMzE2NDcx
NloXDTI4MDIyOTE2NDcxNlowXTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAlRTMQww
CgYDVQQHDANIWUQxEjAQBgNVBAoMCVByaW1lYXV0aDEfMB0GCSqGSIb3DQEJARYQ
aGlAcHJpbWVhdXRoLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMTR6aba5wB03ARS/WxMu9x1OPSJSMPx36ydo1YGni/+07KwGQttLBONq/ghLCKT
9xf7Y8ZbrYoMGrMWOdUw2TgKDfgogKAK0O3tCKM2jJ09laXHOnzUfNJ7qlKRiHr+
S+xZ0YsKmkuZRkyX86Wy5T2WAxXiV+LKENgLo2gFR2VvWqyNiwOQ7sb3AIkVR1Pz
XswLJ0FJ0Hdgju36KfNGSk7obYouOs5BoXSF7eMzQfwf+Me8SwsFhvhqnm8QSw7y
ecJy6B/nOmYHM6zOynqF//w0B36M93pKQ6WPyal/KfCqo9QkO15ZxIP0QMG/ArD0
nnbYv+PK/apwgxC48ZyBhaMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAwzFynT2M
ej0txhiVwhIw7l6E98FGW6tLophUxfLRHn5vopndMCkOFaq6HT03wOAI0mwt1WjL
Z1rTnWebLnn6yq85/l8ZM/nzuYZUx9b7Fa+HcpPTjHgEgfh2Fg55mJcD9xc8MulO
r8TkAK7/vXUCtUCuLBOfwzy450AgfHDRX+iAkt7EM0QETQWpNYqf5n1breQNFes2
XIK2gk/rSrt9lhzT+EqVRS1LaBH0+Iw1RVkh1c9dlysVE6JWmhZ2n0+5GXO40pKP
ksrzXL/50TSpOhpNgh6WC7a72ewS3Ko7XvEcA1xinUuiecYZQ1wPW3JKmC3z68Ar
4I+asfILxPRsSg==
-----END CERTIFICATE-----
CERT

config.secret_key = <<-CERT
-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDE0emm2ucAdNwE
Uv1sTLvcdTj0iUjD8d+snaNWBp4v/tOysBkLbSwTjav4ISwik/cX+2PGW62KDBqz
FjnVMNk4Cg34KICgCtDt7QijNoydPZWlxzp81HzSe6pSkYh6/kvsWdGLCppLmUZM
l/OlsuU9lgMV4lfiyhDYC6NoBUdlb1qsjYsDkO7G9wCJFUdT817MCydBSdB3YI7t
+inzRkpO6G2KLjrOQaF0he3jM0H8H/jHvEsLBYb4ap5vEEsO8nnCcugf5zpmBzOs
zsp6hf/8NAd+jPd6SkOlj8mpfynwqqPUJDteWcSD9EDBvwKw9J522L/jyv2qcIMQ
uPGcgYWjAgMBAAECggEBAIrtcPQqSCx2UGds/R1Y/LIcvFtAHHDTZoM9snGisj5G
rb/PtZ3vLdGPivfW0oSF1UDEXiVByTlMWfxXj/MATBPWZQ3p6QEPIXMQgaxTcOX8
9ojSHGLIymL4j71ApQnMPmNS8yomDcuXIZwnFgC8Sjwyi3MDFe4rm8AkVu+x6jea
KVmxdfXXvPL5SiV6rsg/4snz6AT5ZpQDfREKZtVydv3dpeSYzCdJ6ITYoMPglg5Q
jVNEadPRybvb8xL1B2xc6bK/OjY1pWkdyMjUXgGQFNRli2/Wl1YZkQPDLijIGgjJ
sk9jPUCgxiQbjwhw+LfAB876Sq6kNH38sYvgtBe1wwECgYEA6LwxD8uDwppkNcHK
PLxFFyQhuUbPmqlmGcOJsrxPVKXArd/OGtNsnoPNKFIb22pRE7hSMAfAMgbxyPEB
+W5m23+/vVu9Qf5dwquTsX+gGxAO64jg7Pg4WMMXErumMlwxFLBxs22Ibpngidl5
6hapXHZ98nHxnT3aXnxBusz3pAMCgYEA2H6iY1Dx3RzBjwp4dmZH+UkAPZtf06tC
D5T1opRZJsGLmrsNC5ehXq2C4mgnqOT1MQ2iTVkjNkavcuI4OxXRmZ0p4DSA8Z24
ywnhKpyQ5t4kW4agu/dzbi/EvYDtVquWB9yiDvgzKQpRxAinjK25m2Qb4i5GWxPL
Hcf2j673deECgYEAvAopfcCKMbZ6lvB/jTj0fbEEymTLIgQSaWiSneYGFrdhiVqV
dRkz3pNRNG268jnhThST2xi4EfOIcTlAxh6MXnbGHaG8tVBmwv3L9BLQ8my0EVvj
l7MqG5Vs1AbnTjMsuLGi/DzYibwsLlSXaypqJjnaowOrGse54rN0jBBFWa8CgYBG
tY2aPIzSeBrr+jKAEUX+sI4okP/KZYwNBMz5jdRUaTCMl/1ZxOuKvcca5YPWkPlY
TSiudKegiZOyRRqyiZzMvF06Akv/HlGF1zM4tKxLC1D6p80Ft3t3CJkMf/iEr0Qw
SyqPExe6lsk/6se2leMiUp8cz5phEuTrVC0+npnqYQKBgEBHlTXiFFlkJ023K3Mv
xYg0xGq0puGTGY83hEOjcbyE+NrNjCXXgly8g+MgkPqpMK3vZ9oHZ3/JRNvfi8e1
HuZt5meyj7GkPFQAg/E566H4NZTwFpHt+byOIrqy8ABNu8ITIaE2GS93dR97saqq
+Kxjzwmq9pU25sq3UL93835Z
-----END RSA PRIVATE KEY-----
CERT

  # config.password = "secret_key_password"
  # config.algorithm = :sha256
  # config.organization_name = "Your Organization"
  # config.organization_url = "http://example.com"
  # config.base_saml_location = "#{base}/saml"
  # config.reference_id_generator                                 # Default: -> { UUID.generate }
  # config.attribute_service_location = "#{base}/saml/attributes"
  # config.single_service_post_location = "#{base}/saml/auth"
  # config.session_expiry = 86400                                 # Default: 0 which means never

  # Principal (e.g. User) is passed in when you `encode_response`
  #
  config.name_id.formats # =>
    {                         # All 2.0
      email_address: -> (principal) { principal.email_address },
      transient: -> (principal) { principal.id },
      persistent: -> (p) { p.id },
    }
#   #   OR
#   #
#   #   {
#   #     "1.1" => {
#   #       email_address: -> (principal) { principal.email_address },
#   #     },
#   #     "2.0" => {
#   #       transient: -> (principal) { principal.email_address },
#   #       persistent: -> (p) { p.id },
#   #     },
#   #   }

#   # If Principal responds to a method called `asserted_attributes`
#   # the return value of that method will be used in lieu of the
#   # attributes defined here in the global space. This allows for
#   # per-user attribute definitions.
#   #
#   ## EXAMPLE **
#   # class User
#   #   def asserted_attributes
#   #     {
#   #       phone: { getter: :phone },
#   #       email: {
#   #         getter: :email,
#   #         name_format: Saml::XML::Namespaces::Formats::NameId::EMAIL_ADDRESS,
#   #         name_id_format: Saml::XML::Namespaces::Formats::NameId::EMAIL_ADDRESS
#   #       }
#   #     }
#   #   end
#   # end
#   #
#   # If you have a method called `asserted_attributes` in your Principal class,
#   # there is no need to define it here in the config.

#   # config.attributes # =>
#   #   {
#   #     <friendly_name> => {                                                  # required (ex "eduPersonAffiliation")
#   #       "name" => <attrname>                                                # required (ex "urn:oid:1.3.6.1.4.1.5923.1.1.1.1")
#   #       "name_format" => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", # not required
#   #       "getter" => ->(principal) {                                         # not required
#   #         principal.get_eduPersonAffiliation                                # If no "getter" defined, will try
#   #       }                                                                   # `principal.eduPersonAffiliation`, or no values will
#   #    }                                                                      # be output
#   #
#   ## EXAMPLE ##
#   # config.attributes = {
#   #   GivenName: {
#   #     getter: :first_name,
#   #   },
#   #   SurName: {
#   #     getter: :last_name,
#   #   },
#   # }
#   ## EXAMPLE ##

#   # config.technical_contact.company = "Example"
#   # config.technical_contact.given_name = "Jonny"
#   # config.technical_contact.sur_name = "Support"
#   # config.technical_contact.telephone = "55555555555"
#   # config.technical_contact.email_address = "[email protected]"

  service_providers = {
    "some-issuer-url.com/saml" => {
      fingerprint: "EE:A6:1C:13:8C:37:2C:08:55:2D:D6:8F:5C:32:56:A4:BB:95:26:37:12:62:9E:90:43:B1:42:52:0F:31:A0:28",
      metadata_url: "http://some-issuer-url.com/saml/metadata"
    },
  }

#   # `identifier` is the entity_id or issuer of the Service Provider,
#   # settings is an IncomingMetadata object which has a to_h method that needs to be persisted
#   config.service_provider.metadata_persister = ->(identifier, settings) {
#     fname = identifier.to_s.gsub(/\/|:/,"_")
#     FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
#     File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
#       Marshal.dump settings.to_h, f
#     end
#   }

#   # `identifier` is the entity_id or issuer of the Service Provider,
#   # `service_provider` is a ServiceProvider object. Based on the `identifier` or the
#   # `service_provider` you should return the settings.to_h from above
#   config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
#     fname = identifier.to_s.gsub(/\/|:/,"_")
#     FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
#     full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
#     if File.file?(full_filename)
#       File.open full_filename, "rb" do |f|
#         Marshal.load f
#       end
#     end
#   }

#   # Find ServiceProvider metadata_url and fingerprint based on our settings
#   config.service_provider.finder = ->(issuer_or_entity_id) do
#     service_providers[issuer_or_entity_id]
#   end
end

undefined method `name_id' for #<struct request_id=nil>

When I try to logout via a link on my service provider, the idp rails app throws 500 saying undefined method 'name_id' for #<struct request_id=nil>.

versions:

ruby 2.4.1
rails 5.2.3
saml_idp 0.8.0

log:

I, [2020-04-23T06:09:47.614360 #4086]  INFO -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9] Started POST "/saml/logout" for 118.86.255.213 at 2020-04-23 06:09:47 +0000
I, [2020-04-23T06:09:47.617226 #4086]  INFO -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9] Processing by SamlIdpController#logout as HTML
I, [2020-04-23T06:09:47.617365 #4086]  INFO -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9]   Parameters: {"SAMLRequest"=>"PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxuczA6TG9nb3V0UmVxdWVzdCB4bWxuczpuczA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6bnMxPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiB4bWxuczpuczI9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIElEPSJpZC12YmNrbjJPaWNhYnNTQjZweCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDQtMjNUMDY6MDk6NDdaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly93d3cuZ2xvYmFsaW5ub3ZhdGlvbmV4Y2hhbmdlLm9yZy9zYW1sL2xvZ291dCIgUmVhc29uPSIiPjxuczE6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwczovL2lubm92YXRpb25zLnNtYXBwbHkuaW8vc2FtbDIvbWV0YWRhdGEvPC9uczE6SXNzdWVyPjxuczI6U2lnbmF0dXJlIElkPSJTaWduYXR1cmUxIj48bnMyOlNpZ25lZEluZm8+PG5zMjpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PG5zMjpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz48bnMyOlJlZmVyZW5jZSBVUkk9IiNpZC12YmNrbjJPaWNhYnNTQjZweCI+PG5zMjpUcmFuc2Zvcm1zPjxuczI6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48bnMyOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvbnMyOlRyYW5zZm9ybXM+PG5zMjpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxuczI6RGlnZXN0VmFsdWU+dHJIWXcyYlFzeWpGQldHNHIxbFRsVzNQT2lVPTwvbnMyOkRpZ2VzdFZhbHVlPjwvbnMyOlJlZmVyZW5jZT48L25zMjpTaWduZWRJbmZvPjxuczI6U2lnbmF0dXJlVmFsdWU+WGxrTzJBVUMvMHZRbkZ4aEUrVUhaM2tjdTdxaE1obkRRVEtBaE1ZajVMWFlOMEdNT08zczRaUnpQSnZzaXZjawpPUTMyWHJ6cVhXSVpqcmxBeHVRSVNkSW1jWjlYUHFlTk9FWEJYVUV3akZJdzcwandTMVhJUlArb0RsYVBadEF1CjV1cWlSdk1FbkJsOG1HQXFwVnpLSkFhdW9lS3ZPOHE3RzZDNGNINlN4NUtXUlh1eFBVKzljbWYwcEpnVFdtYXQKdDVZMWllSWVoTHpIemlqYmQzeVpYeUZMYjBybGpQYnlwWWJSNk1IWWRzSmxtekw5ME9YVmJjRFNUUVJKSzNJZwpLOUZRSWdMSmM3czFhZk12N1NxTEJ0TnN3cFhxODBuYnJXVHMwYVpGOFYyWkFWdnlsWlpPTFZOMWJQU28yRHhRClN2eHdCa29zRFpXdkMvY3ZRdjM3b2UyYzRLVHhndzRhajBKcCtYb2dRU0JGOUJIcm5TZjBOQWJLaUFnY1JESjMKeHpsakt2YUFCNkpWOWxKeUthMDJtZ2QzV0cyTjdBcHkzNEpLZkJRSTFLc3MrcXE0MG9BbTJEYXdwNS9TaXB1ZQpOM1pJYmZ1cjRsbWU5SldhYng4M3oybWZRVzExRWJZQ0FEbjV3RktIQkJ2dFFIOHRSQkd4ZndSME15Z2E5b0xpCk5tL0VxYVNtT3VuK295ejhjVURSV2ltWHdPc2ZBR1BXU3dIZ251Y2lmL2VYZyt2QWIxWldNNHQ2cm0zMXlVMWwKRnFLVjVwSDRSQkltTnpTaWQyYkpjRkxsNFZhM3lUVGZ6eDlmWGF0a2orb0ZUWjRsWU1LSVpTaXJMVDN4RDBwNwpHTDY1bjMwa1hQZW16Z2ZBaHBxSXFNSVBGZjd4Q0ExR0dvU1ZrT2YvaFFRPTwvbnMyOlNpZ25hdHVyZVZhbHVlPjxuczI6S2V5SW5mbz48bnMyOlg1MDlEYXRhPjxuczI6WDUwOUNlcnRpZmljYXRlPk1JSUZ0ekNDQTUrZ0F3SUJBZ0lKQU41OWJMdVQ1MEUzTUEwR0NTcUdTSWIzRFFFQkN3VUFNSEl4Q3pBSkJnTlZCQVlUQWtOQk1Rc3dDUVlEVlFRSURBSlBUakVWTUJNR0ExVUVDZ3dNVTNWeWRtVjVUVzl1YTJWNU1SZ3dGZ1lEVlFRRERBOW1iSFZwWkhKbGRtbGxkeTVqYjIweEpUQWpCZ2txaGtpRzl3MEJDUUVXRm01aGRHVmlRSE4xY25abGVXMXZibXRsZVM1amIyMHdIaGNOTVRZeE1qRTVNakV5TWpNd1doY05Nall4TWpFM01qRXlNak13V2pCeU1Rc3dDUVlEVlFRR0V3SkRRVEVMTUFrR0ExVUVDQXdDVDA0eEZUQVRCZ05WQkFvTURGTjFjblpsZVUxdmJtdGxlVEVZTUJZR0ExVUVBd3dQWm14MWFXUnlaWFpwWlhjdVkyOXRNU1V3SXdZSktvWklodmNOQVFrQkZoWnVZWFJsWWtCemRYSjJaWGx0YjI1clpYa3VZMjl0TUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUF4bTVmVnFzOW5XYitKdCtOVitNYlp2eU9zdEY1YUJqbXJ1UnBBbEQ4bW54ZmM1S1JFdVZHYkNudWF4ZHFSb1ZUcnlseldwUEU1My9hSUFna2hOWUxaeSswVFBVQnNpZ29IME5GWDdZQmp2WlhFaE16QXF0aFVPYTJ2djFLZWlTbGhUMTl4akU1TGR0SDVtUHhEVFZZdkFHM0UwbXVqQVZ5NlA2Y20wQ3JtcVVvVUhueHZmMTVXWlcwNUlxbk1uWExEVkpWcEpHVk5ZSFhZTDEvZUxidzFlSUZsQXFianFGTVI4cjEweDBDdmlVL3NGWURFU1JQNzZrdmU5NEFuYnFhK0tBeU0yNTJNU2Mvck9LNnh5ajhaM3lkQ09JRC9nRUM0WnR4K1pPUk95eVVhWTZJMll4ZnpuT2x3MWhqYVJ6SUR4Z2JQeXRTRVVpK0VjTk8xWmJJVGFXa3llQlhkTmRBKzhXbm5MWFBpTkNFWmF0aGVWbzdwbmdIaDZsbzFnWTJUWnNOZjA4M1lNc05GbW41TUgxSTdGblVNSWc1S0NnbllMTGZCb1pNWjhHZllGcVVYSGZFOTE3WENxT29FVnBFVmxJVHZlZVMweFQ5bU5JaG1WaE03cHJldVNzeGFBdVFVNjFKSUViZFk0M1BpZUxwUkpnQnQ0MmNuYUN2aFpYSHNDei9lWmhxN2lleFQ0Z3JYN0JxeEhJaS9TencwV1VmdzZJeU1lOHVxNGEvbEpHKzRhWkYxR2N1T0lBLzYvNnBlTGRyVys3RVpmM2pmZ0cyMXpYUWpxUVlSVUE3ci9GS3dFR0luaEk5QmU3SVhWZ2FGUXlXV2dBMVVObEJpaDVMcW9KYTBFeURTQnFZeDE0OTRYZStIVDFSZ1k5UitxdVBKS0Uxbk1lMi9Zc0NBd0VBQWFOUU1FNHdIUVlEVlIwT0JCWUVGT1F2Qy9jMUc2U2tHbGV4bDJPMG5YS1JVOHJRTUI4R0ExVWRJd1FZTUJhQUZPUXZDL2MxRzZTa0dsZXhsMk8wblhLUlU4clFNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFDUEdsNUJxVVZzQUFNM0c4ODB0RXFlMk5IdFcydzIxcHdETnRZLzgvZkVFdlBkeDc0YkFVc3RVTDhFelhSYzR4bUxmOS9zcFpTcmR3alJVWjBldDVmWDNLOXA3MnVtdHdmVk00NG52V2VVK1VvUVkwVjYzYUI0U1lBRFhRbytnd3lCZmZsV2hSWkpCdHZnK1N2NmJaOUNaZHNKM2RtTDM4WjNWM2dIY0dhS2s5UDdkUmI5a2VvSkNZRjFGOHQyM2VKZW00QmRFSzhCMERjV0JNVVFjT2RjZHNlbHIzUFpIekxiakUxQXVMYVFWZk1Ua3NWbzErUi9jZDFOaGF3aDlIaFkwRjdzcVc2VWl3NkJ6dTRVTmR6NldjV1Z6TnNJdkZLMmxNcVl3Zkx5N0haWFlyRENKNVY1N2NCQXZRN2hocUY4K2FOdS9nR1dnVXA3Q1ZBRWhHZGtuV202VWtMM1VNUUlvdG04Q3RXN0krZHJBYmVvbFVJSktNQTM0N1ZOcytKREM3d3FLSkozT0xkS2tTdFVzRVNocXFjcXRFb05NZDFkM2dtOU1vUlBPTm40cklpYWVuVnFtT1djVklBWW9CVFJjQVdiV2d5cjB4ZXNqSjlESWdHT3Z3eG1KSmk0RWpuTHVRWEVpQ29VbjlaM3pveTZ2MnBjRGsvUjcyV01YUmpFUmZyMk1GQ1E3VlBqSWRZWTRoZVM5TzVjWnI5bnNlN2pSTUljK3locjI4RHpOSWZmQk9CZjdTT2hHSFllemc4dmNUNFZrU1ZpWWdneElGUEROdTZ1dFVBQTFoWTNUZDFQNjQxY3NKSzZyYkFTd29ReTFhTituQzFLclRRTmNmaE0wRnJvUENXZ1lDVGNnWHdUMTMzOG1scktYenV4bXRlRkpHVzV1eHM0NjwvbnMyOlg1MDlDZXJ0aWZpY2F0ZT48L25zMjpYNTA5RGF0YT48L25zMjpLZXlJbmZvPjwvbnMyOlNpZ25hdHVyZT48bnMxOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVtYWlsIj5tYXNha2F6dUBla29oZS5jb208L25zMTpOYW1lSUQ+PG5zMDpTZXNzaW9uSW5kZXg+X2YwZDUzNDQwLTY3NTYtMDEzOC0zM2QzLTBlNjQ4OTYzM2UwMDwvbnMwOlNlc3Npb25JbmRleD48L25zMDpMb2dvdXRSZXF1ZXN0Pgo=", "RelayState"=>"id-vbckn2OicabsSB6px|1587622187|ef38f521560c49d481e7da6ba71bcda6c509a283"}

I, [2020-04-23T06:09:47.628511 #4086]  INFO -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9] Completed 500 Internal Server Error in 11ms (ActiveRecord: 1.4ms)
F, [2020-04-23T06:09:47.629469 #4086] FATAL -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9]
F, [2020-04-23T06:09:47.630988 #4086] FATAL -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9] NoMethodError (undefined method `name_id' for #<struct request_id=nil>):
F, [2020-04-23T06:09:47.631038 #4086] FATAL -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9]
F, [2020-04-23T06:09:47.631072 #4086] FATAL -- : [275a4a9f-38f1-45a3-bd46-75eee09441c9] app/controllers/saml_idp_controller.rb:49:in `idp_logout'
[275a4a9f-38f1-45a3-bd46-75eee09441c9] app/controllers/saml_idp_controller.rb:85:in `logout'

config/initializers/saml.rb

SamlIdp.configure do |config|
  base = $secrets.dig(:survey_monkey, :base_url)

  config.x509_certificate = <<EOS.strip
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
EOS

  config.secret_key = <<EOS
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
EOS

  # config.password = "my_secret_password"
  config.algorithm = :sha256
  config.organization_name = "..."
  config.organization_url = base
  config.base_saml_location = "#{base}/saml"
  config.reference_id_generator                                 # Default: -> { UUID.generate }
  config.single_logout_service_post_location = "#{base}/saml/logout"
  config.single_logout_service_redirect_location = "#{base}/saml/logout"
  config.attribute_service_location = "#{base}/saml/attributes"
  config.single_service_post_location = "#{base}/saml/auth"
  # config.session_expiry = 86400                                 # Default: 0 which means never

  # Principal (e.g. User) is passed in when you `encode_response`
  #
  config.name_id.formats = {                         # All 2.0
    email: -> (principal) { principal.email },
    transient: -> (principal) { principal.id },
    persistent: -> (p) { p.id },
  }

  config.technical_contact.company = "..."
  config.technical_contact.given_name = "..."
  config.technical_contact.sur_name = "..."
  config.technical_contact.telephone = ""
  config.technical_contact.email_address = "..."

  service_providers = {
    "https://innovations.smapply.io/saml2/metadata/" => {
      fingerprint: "...",
      metadata_url: "https://innovations.smapply.io/saml2/metadata/",
      response_hosts: ["..."]
    },
  }

  config.service_provider.metadata_persister = ->(identifier, settings) {
    fname = identifier.to_s.gsub(/\/|:/, "_")
    File.open Rails.root.join("public/#{fname}"), "r+b" do |f|
      Marshal.dump settings.to_h, f
    end
  }

  config.service_provider.persisted_metadata_getter = ->(identifier, service_provider) {
    fname = identifier.to_s.gsub(/\/|:/, "_")
    full_filename = Rails.root.join("public/#{fname}")
    if File.file?(full_filename)
      File.open full_filename, "rb" do |f|
        Marshal.load f
      end
    end
  }

  config.service_provider.finder = ->(issuer_or_entity_id) do
    service_providers[issuer_or_entity_id]
  end
end

config/routes.rb

Rails.application.routes.draw do
...
 match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
...
end

app/controllers/samlidp_controller.rb

# frozen_string_literal: true

class SamlIdpController < ApplicationController
  include SamlIdp::Controller
  SamlIdpLogger = Logger.new Rails.root.join("log/sm.log")

  protect_from_forgery prepend: true, with: :exception, except: %w(logout)

  ...

  def idp_authenticate(email, password)
    user = User.find_by(email: email)
    user && user.valid_password?(password) ? user : nil
  end
  private :idp_authenticate

  def idp_make_saml_response(found_user)
    sm_track_log found_user
    encode_response found_user
  end
  private :idp_make_saml_response

  def idp_logout
    user = User.find_by(email: saml_request.name_id)
    user.logout
  end
  private :idp_logout

  def logout
    idp_logout
    @saml_response = idp_make_saml_response(nil)
    render template: "saml_idp/idp/saml_post", layout: false
  end
end

metadata_persister is never called

The metadata_persister does not seem to be getting hit in any step of my code. Therefore i have not metadata saved for my SPs. Is there a configuration step that I am missing? Struggling to debug this issue, at login I am receiving an "Issuer cannot be verified." error but there is not enough information to debug.

Can this gem be used to implement IdP initiated SSO?

Hey,

I'm trying to implement IdP initiated SSO where my rails app will be the identity provider.

I've already sent a list of users over to the service provider who should be allowed access. What I need to do now is allow a logged in user in our app to click a button and POST a blob of XML to the service provider's ACS endpoint, without having received an authn request from the service provider first.

I think this gem should be able to do that, but I just can't figure it out ๐Ÿ˜… Can somebody help me out?

Thanks ๐Ÿ’™

Proposal: Add new options to dynamically change Name ID and attributes depending on SP

Problem

Under the current implementation of this library, the value of Name ID in a SAML response is retrieved by a lambda function defined in /config/initializers/saml_idp.rb, and the values of asserted attributes in the SAML response are given by other lambda functions defined in either the configuration file or principal.asserted_attributes.

The problem I'm facing now is that it's not always the case that each SP requires the same
value as Name ID.

For example, G-Suite requires an e-mail address as Name ID, but Zoom allows arbitrary letters for Name ID.
While I was developing my own Idp, I want the flexibility to have users choose which value to use for Name ID for service providers like Zoom.

The same argument is applied to asserted attributes in a SAML response.
The current implementation always produces a SAML response with all attributes
defined in either the configuration file or principal.asserted_attributes,
but some services such as G-Suite return an authentication error if a SAML response
includes unnecessary(undefined) attributes even when the request contains a valid Name ID.

Hence, it would be nicer if we could choose which attributes to populate in a SAML response for each SP.

Proposal

Adding 2 new options in encode_response when creating a SAML response in SamlIdpController like so:

  def create
    @saml_response = encode_response(current_user, {
        name_id_formats: {
            persistent: -> (principal) {
              principal.unique_identifier
            }
        },
        attributes: {
            GivenName: {
                getter: :first_name
            },
            SurName: {
                getter: :last_name
            }
        }
    })
    render template: "saml_idp/idp/saml_post", layout: false
  end

This enables your IDP to populate both Name ID and attributes on the fly, depending on SPs and your user's configurations.

Since these are just optional values, this change wouldn't break the existing behaviors.

I'll start working on this issue in my branch, as I need this feature anyway.
I'd be really happy to hear any feedback and discuss this issue.

Thanks in advance.

Outdated Readme

isn't it supposed to be

< SamlIdp::IdpController

instead of

include SamlIdp::IdpController

Consider a move to Github Actions

It's taken about 30 minutes for travis to pick up a build much less run the matrix. Worth considering trying to move to Github actions if only to have a more first-party integration.

Update ruby-saml gem dependency to 1.7 to patch new SAML vulnerability.

OneLogin just alerted its users to a new SAML vulnerability. They have already patched their ruby-saml gem in version 1.7 and this gem should now be referencing that version (

s.add_development_dependency('ruby-saml', '>= 1.5')
).

Here's the patch in onelogin/ruby-saml: SAML-Toolkits/ruby-saml@048a544

I can open a PR for this change if you'd like but hopefully this can get patched as soon as possible!

Thank you.

metadata_persister does not appear to be called

This issue is along the lines of #33 and #37, but I am not getting issues with the authentication process. I am trying to implement IdP initiated logout and to do that I am exposing the logout url in the SP metadata (as recommended by omniauth-saml here).

It looks like all of the wiring for fetching the SP metadata is there (especially in the ServiceProvider object), but at no point in the code does any of that appear to be used.

Right now, it appears the solution would be to call @saml_request.service_provider.refresh_metadata somewhere in the controller concern.

@Yanchek99 pinging you here as you've opened several issues around the subject that have been closed.

No way to declare a HTTP-POST SingleSignOnService

There seems to be no way to declare a HTTP-POST binding for SingleSignOnService.

Furthermore, the confusingly-named config item single_service_post_location appears to be used as the location for the HTTP-Redirect binding for SingleSignOnService...

Support for certificate rotation

SAML IdPs often rotate their signing keys. From my understanding, this is done by publishing two certs to the metadata endpoint in parallel for some period of time to allow service providers to validate against both. This avoids an outage of the service provider when the signing cert is changed.

From what I can tell, this gem does not support publishing two certs in parallel to the metadata endpoint. Is that accurate? Would you take a feature request to make this possible?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.