Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/controllers/saml_idp/idp_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class IdpController < ActionController::Base
protect_from_forgery

before_filter :validate_saml_request
skip_before_filter :validate_saml_request, :only => [:logout]
before_filter :validate_saml_slo_request, :only => [:logout]

def new
render :template => "saml_idp/idp/new"
Expand All @@ -27,6 +29,18 @@ def create
render :template => "saml_idp/idp/new"
end

def logout
_person, _logout = idp_slo_authenticate(params[:name_id])
if _person && _logout
@saml_slo_response = idp_make_saml_slo_response(_person)
else
@saml_idp_fail_msg = 'User not found'
logger.error "User with email #{params[:name_id]} not found"
@saml_slo_response = encode_SAML_SLO_Response(params[:name_id])
end
render :template => "saml_idp/idp/saml_slo_post", :layout => false
end

protected

def idp_authenticate(email, password)
Expand All @@ -37,5 +51,13 @@ def idp_make_saml_response(person)
raise "Not implemented"
end

def idp_slo_authenticate(email)
raise "Not implemented"
end

def idp_make_saml_slo_response(person)
raise "Not implemented"
end

end
end
13 changes: 13 additions & 0 deletions app/views/saml_idp/idp/saml_slo_post.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
</head>
<body onload="document.forms[0].submit();" style="visibility:hidden;">
<%= form_tag(@saml_slo_acs_url) do %>
<%= hidden_field_tag("SAMLResponse", @saml_slo_response) %>
<%= submit_tag "Submit" %>
<% end %>
</body>
</html>
44 changes: 40 additions & 4 deletions lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,48 @@ def encode_SAMLResponse(nameID, opts = {})
Base64.encode64(xml)
end

def validate_saml_slo_request(saml_request = params[:SAMLRequest])
decode_SAML_SLO_Request(saml_request)
end

def decode_SAML_SLO_Request(saml_request)
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
@saml_slo_request = zstream.inflate(Base64.decode64(saml_request))
zstream.finish
zstream.close
@saml_slo_request_id = @saml_slo_request[/ID=['"](.+?)['"]/, 1]
@saml_slo_acs_url = @saml_slo_request[/AssertionConsumerLogoutServiceURL=['"](.+?)['"]/, 1]
end

def encode_SAML_SLO_Response(nameID, opts = {})
now = Time.now.utc
response_id, reference_id = UUID.generate, UUID.generate
audience_uri = opts[:audience_uri] || @saml_slo_acs_url[/^(.*?\/\/.*?\/)/, 1]
issuer_uri = opts[:issuer_uri] || (defined?(request) && request.url.split("?")[0]) || "http://example.com"

assertion = %[<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{reference_id}" IssueInstant="#{now.iso8601}" Version="2.0"><Issuer>#{issuer_uri}</Issuer><Subject><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{nameID}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="#{@saml_slo_request_id}" NotOnOrAfter="#{(now+3*60).iso8601}" Recipient="#{@saml_slo_acs_url}"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore="#{(now-5).iso8601}" NotOnOrAfter="#{(now+60*60).iso8601}"><AudienceRestriction><Audience>#{audience_uri}</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"><AttributeValue>#{nameID}</AttributeValue></Attribute></AttributeStatement><AuthnStatement AuthnInstant="#{now.iso8601}" SessionIndex="_#{reference_id}"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>]

digest_value = Base64.encode64(algorithm.digest(assertion)).gsub(/\n/, '')

signed_info = %[<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-#{algorithm_name}"></ds:SignatureMethod><ds:Reference URI="#_#{reference_id}"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig##{algorithm_name}"></ds:DigestMethod><ds:DigestValue>#{digest_value}</ds:DigestValue></ds:Reference></ds:SignedInfo>]

signature_value = sign(signed_info).gsub(/\n/, '')

signature = %[<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">#{signed_info}<ds:SignatureValue>#{signature_value}</ds:SignatureValue><KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>#{self.x509_certificate}</ds:X509Certificate></ds:X509Data></KeyInfo></ds:Signature>]

assertion_and_signature = assertion.sub(/Issuer\>\<Subject/, "Issuer>#{signature}<Subject")

xml = %[<samlp:LogoutResponse ID="_#{response_id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{@saml_slo_acs_url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="#{@saml_slo_request_id}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">#{issuer_uri}</Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status>#{assertion_and_signature}</samlp:LogoutResponse>]

Base64.encode64(xml)
end

private

def sign(data)
key = OpenSSL::PKey::RSA.new(self.secret_key)
Base64.encode64(key.sign(algorithm.new, data))
end
def sign(data)
key = OpenSSL::PKey::RSA.new(self.secret_key)
Base64.encode64(key.sign(algorithm.new, data))
end

end
end