saml-idp / saml_idp

Ruby SAML Identity Provider, best used with Rails (though not required)
MIT License
265 stars 182 forks source link

Examples? such as a devise example #23

Open TJM opened 9 years ago

TJM commented 9 years ago

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

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.

jphenow commented 9 years ago

I love this idea! Even just a Devise example would be a great start for a ton of people I would think.

If you want to submit a PR that adds this directory and outlines how to do this within one file or a set of files, that'd be great.

TJM commented 9 years ago

I am thinking a directory of markdown docs, sort of like above, provide a fairly simple way of sprinkling documentation and code together into a single file. I might add Prerequisites or something? The thing that hurt me the worst (besides being a fairly newb ruby guy) was the initializer. I had to go through a lot of stack traces to figure out what was going wrong. It kept trying to call ".persistent" against my User object, and I couldn't figure out why :). Anyhow, I will see if I can carve off another half hour or something to polish this up.

jphenow commented 9 years ago

That'd be Great, thanks a bunch for anything you can contribute!

Yanchek99 commented 8 years ago

Would also be nice to provide examples on how to test this with rspec/minitest. Running in circles trying to write tests for the IDP controller.

MatthewBartlett commented 8 years ago

Did any of these examples ever materialise? I'm finding it difficult to understand the configuration requirements.

At the moment my service provider is never trying to get fresh metadata, and there is no persisted metadata. Validation of the request then fails due to missing acs_url. I've added a base, defined the service provider name and metadata URL, it just never calls refresh metadata.

Appreciate any help or examples!

-Matt

Yanchek99 commented 8 years ago

Also note that the Devise Example above only works if using the SAML Http redirect protocol. If using the HTTP post method this does not work, as you will never be redirected back to the sp.

rayson1223 commented 8 years ago

@TJM : Did you solve the ".persistent" undefined method for the User model object in Devise?

TJM commented 8 years ago

@rayson1223 - I will admit that I am the "IT" guy, and I was just part of the project to help with the SAML stuff. I know "it works" for us, but use it at your own risk. The only place I see "persistent" in our initializer is:

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

There is also some stuff further down about metadata, but I think its unrelated.

vipera commented 3 years ago

I've found an issue with this type of Devise usage. When you use:

before_action :authenticate_user!

To redirect incoming SAML auth requests to Devise's session controllers, you will easily exceed the cookie size limit if the SP is sending a signed authn request. For example, I'm using devise + omniauth-saml on the SP side with the following rather ordinary settings (Gitlab uses similar settings):

security: {
  authn_requests_signed: true,
  logout_requests_signed: true,
  logout_responses_signed: true,
  want_assertions_signed: true,
  metadata_signed: true,
  embed_sign: true,
  signature_method: XMLSecurity::Document::RSA_SHA256,
  digest_method: XMLSecurity::Document::SHA256,
}

If authn_requests_signed is enabled, then before redirecting to Devise's sign in page, the URL containing the SAMLRequest parameter will fail to be stored in the session for use as return URL. This will be too large to be persisted in a session cookie jar.

Since saml_idp required signed logout requests to support SP-initated SLO, I've found that you have to at least exclude the logout action from authentication:

before_action :authenticate_user!, except: %i[logout]

I've not yet found a solution for enabling signed authn requests apart from switching to another type of session storage on the IdP.

I assume the ideal thing would be to redirect around Devise's session controllers while always persisting the SAMLRequest in the querystring, then after logging in or signing up getting redirected back (this time with a principal yielded by Devise when calling current_*) everything would work. I don't know my way around Devise/Warden enough to know whether this is possible. I'm going to do some more digging.

JohnKacz commented 2 years ago

@vipera were you able to figure anything out. I've got the same issue I believe.

vipera commented 2 years ago

@JohnKacz I have currently set authn_requests_signed to false in the SP configuration. This "fixes" the issue by shortening the SAMLRequest payload. My IdP is currently used only by internally developed SP applications, so I'm permitting unsigned requests from those sources.

Alternatively, you could look into using Memcached or Redis as the session store within the saml_idp application. Then you should not run into this problem as you can store more data in the session store. I understand that CookieStore has its benefits and that this might not be practical if you're not already using either for other caching.

So basically I didn't manage to get to the bottom of it, I worked around it. I didn't have the time to tackle it properly, but if anyone has any pointers I'd be glad to try and make it work with cookie-stored sessions.

Zogoo commented 2 years ago

@JohnKacz, @vipera I'm not sure why authn_requests_signed is affecting failure of the non authenticated SAML request. We need to do more investigation about this issue. If you guys could provide more information like logs or whatever to help to speed up the investigation that would be great.

About cookie store issue is cookie only allows to store 4kb but the SAML request is too big to store it. Instead of that you guys can store SP initiated SAML request URL (not request itself) for SAML request and let "devise" redirect back to SP url again after successfully logged into your IdP.

mrobock commented 2 years ago

I had the same issue and wasn't able to get devise to help so I modified the validate_saml_request before action and set the SAMLRequest to the session on post and then pulled it back out on get.

def validate_saml_request
      if request.get?
        params["SAMLRequest"] = session["SAMLRequest"]
      elsif request.post?
        session["SAMLRequest"] = params["SAMLRequest"]
        store_location_for("user", "#{request.base_url}/saml/auth") if current_user.blank?
      end

      super
    end

I'm on a forked branch of 0.9.0

Zogoo commented 2 years ago

@mrobock your code has a hint. I would like to clarify one thing who also looking for some solution. SAML has also specification GET request (not only POST), people who uses your way need to be aware of SP has only support POST request. And I think you really don't need to override the method since you have full management of your own session controller, or application controller of a Rails app.