Giter Club home page Giter Club logo

libsaml's Introduction

Build status Coverage status Code climate Dependency status

libsaml

Libsaml is a Ruby gem to easily create SAML 2.0 messages. This gem was written because other SAML gems were missing functionality such as XML signing.

Libsaml's features include:

  • Multiple bindings:
    • HTTP-Post
    • HTTP-Redirect
    • HTTP-Artifact
    • SOAP
  • XML signing and verification
  • Pluggable backend for providers (FileStore backend included)

Copyright Digidentity B.V., released under the MIT license. This gem was written by Benoist Claassen.

Installation

Place in your Gemfile:

gem 'libsaml', require: 'saml'

Usage

Below follows how to configure the SAML gem in a service provider.

Store the private key in: config/ssl/key.pem

Store the public key of the identity provider in: config/ssl/trust-federate.cert

Add the Identity Provider web container configuration file to config/metadata/service_provider.xml.

This contains an encoded version of the public key, generate this in the ruby console by typing:

require 'openssl'
require 'base64'

pem = File.open("config/ssl/trust-federate.cert").read
cert = OpenSSL::X509::Certificate.new(pem)
output = Base64.encode64(cert.to_der).gsub("\n", "")

Add the Service Provider configuration file to: config/metadata/service_provider.xml:

<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor ID="_052c51476c9560a429e1171e8c9528b96b69fb57" entityID="my:very:original:entityid" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
  <md:SPSSODescriptor>
    <md:KeyDescriptor use="signing">
      <ds:KeyInfo>
        <ds:X509Data>
          <ds:X509Certificate>SAME_KEY_AS_GENERATED_IN_THE_CONSOLE_BEFORE</ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </md:KeyDescriptor>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Post" Location="http://localhost:3000/saml/receive_response" index="0" isDefault="true"/>
  </md:SPSSODescriptor>
</md:EntityDescriptor>

Set up an intializer in config/initializers/saml_config.rb:

Saml.setup do |config|
  config.register_store :file, Saml::ProviderStores::File.new("config/metadata", "config/ssl/key.pem"), default: true
end

By default this will use a SamlProvider model that uses the filestore, if you want a database driven model comment out the #provider_store function in the initializer and make a model that defines #find_by_entity_id:

class SamlProvider < ActiveRecord::Base
  include Saml::Provider

  def self.find_by_entity_id(entity_id)
    find_by entity_id: entity_id
  end
end

Now you can make a SAML controller in app/controllers/saml_controller.rb:

class SamlController < ApplicationController
  extend Saml::Rails::ControllerHelper
  current_provider "entity_id"

  def request_authentication
    provider = Saml.provider("my:very:original:entityid")
    destination = provider.single_sign_on_service_url(Saml::ProtocolBinding::HTTP_POST)

    authn_request = Saml::AuthnRequest.new(:destination => destination)

    session[:authn_request_id] = authn_request._id

    @saml_attributes = Saml::Bindings::HTTPPost.create_form_attributes(authn_request)

    render text: @saml_attributes.to_yaml
  end

  def receive_response
    if params["SAMLart"]
      # provider should be of type Saml::Provider
      @response = Saml::Bindings::HTTPArtifact.resolve(request, provider.artifact_resolution_service_url)
    elsif params["SAMLResponse"]
      @response = Saml::Bindings::HTTPPost.receive_message(request, :response)
    else
       # handle invalid request
    end

    if @response && @response.success?
      if session[:authn_request_id] == @response.in_response_to
        @response.assertion.fetch_attribute('any_attribute')
      else
        # handle unrecognized response
      end
      reset_session # It's good practice to reset sessions after authenticating to mitigate session fixation attacks
    else
      # handle failure
    end
  end
end

Don't forget to define the routes in config/routes.rb:

  get "/saml/request_authentication" => "saml#request_authentication"
  get "/saml/receive_response" => "saml#receive_response"
  post "/saml/receive_response" => "saml#receive_response"

Using libsaml as an IDP

Writing a solid identity provider really requires a deeper knowledge of the SAML protocol, so it's recommended to read more on the SAML 2.0 Wiki http://en.wikipedia.org/wiki/SAML_2.0. When you understand what it says, read these parts of the specification: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf

Below is an example of a very primitive IDP Saml Controller

class SamlController < ActionController::Base
  extend Saml::Rails::ControllerHelper
  current_provider "entity_id"

  def receive_authn_request
    authn_request = if request.get?
      Saml::Bindings::HTTPRedirect.receive_message(request, type: :authn_request)
    elsif request.post?
      Saml::Bindings::HTTPPost.receive_message(request, :authn_request)
    else
      return head :not_allowed
    end
    request_id = authn_request._id

    session[:saml_request] = {
      request_id:    request_id,
      relay_state:   params['RelayState'],
      authn_request: authn_request.to_xml
    }

    if authn_request.invalid?
      redirect_to send_response_path(request_id: request_id)
    else
      redirect_to sign_in_path(return_to: send_response_path(request_id: request_id))
    end
  end

  def send_response
    return head :not_found if session[:saml_request][:request_id] != params[:request_id]

    authn_request = Saml::AuthnRequest.parse(session[:saml_request][:authn_request], single: true)

    response = if authn_request.invalid?
      build_failure(Saml::TopLevelCodes::REQUESTER, Saml::SubStatusCodes::REQUEST_DENIED)
    elsif account_signed_in?
      build_success_response
    else
      build_failure(Saml::TopLevelCodes::RESPONDER, Saml::SubStatusCodes::NO_AUTHN_CONTEXT, 'cancelled')
    end

    if authn_request.protocol_binding == Saml::ProtocolBinding::HTTP_POST
      # render an auto submit form with hidden fields set in the attributes hash
      @attribute = Saml::Bindings::HTTPPost.create_form_attributes(response, relay_state: session[:saml_request][:relay_state])
    else
      # handle unsupported binding
    end
  end

  private

  def build_failure(status_value, sub_status_value, status_detail)
    Saml::Response.new(in_response_to:   session[:saml_request][:request_id],
                       status_value:     status_value,
                       sub_status_value: sub_status_value,
                       status_detail:    status_detail)
  end

  def build_success_response(authn_request)
    assertion = Saml::Assertion.new(
      name_id:                 current_account.username, # Return anything that you can link to an account
      name_id_format:          'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
      authn_context_class_ref: Saml::ClassRefs::PASSWORD_PROTECTED,
      in_response_to:          authn_request._id,
      recipient:               authn_request.assertion_url,
      audience:                authn_request.issuer)

    # adding custom attributes
    assertion.add_attribute('name', 'value')

    Saml::Response.new(in_response_to: authn_request._id,
                       assertion:      assertion,
                       status_value:   Saml::TopLevelCodes::SUCCESS)
  end
end

Contributing

  • Fork the project
  • Contribute your changes. Please make sure your changes are properly documented and covered by tests.
  • Send a pull request

libsaml's People

Contributors

benoist avatar kevintuhumury avatar dannyvanderheiden avatar jandintel avatar nov avatar jdongelmans avatar aaiiit avatar pzgz avatar middagj avatar lawliet89 avatar nddeluca avatar jonathan avatar aaaknowit avatar buffym avatar gyng avatar pieterlange avatar sahild avatar

Watchers

 avatar James Cloos avatar

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.