Skip to content

Commit

Permalink
Initiate IdP on demand
Browse files Browse the repository at this point in the history
  • Loading branch information
zogoo committed Jan 3, 2024
1 parent 9fb38ca commit 43a9b2d
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 205 deletions.
253 changes: 133 additions & 120 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Ruby SAML Identity Provider (IdP)

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

[![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)

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](http://en.wikipedia.org/wiki/Security_Assertion_Markup_Language)
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.
This gem not manage any state of SAML configuration each requests that you want to respond to SP (Service Provider) you need to load SP's metadata and generate response according SAML 2.0 specification.

[![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)

This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real SAML IDP implementation.
Forked from <https://github.com/lawrencepit/ruby-saml-idp>

## Installation and Usage

Expand All @@ -20,146 +20,159 @@ Add this to your Gemfile:
gem 'saml_idp'
```

### Not using rails?
## 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
Basically you need configure your IdP provider information for your SP provider with `configure` (see Configuration)
method and then `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?
## Using rails?

Check out our Wiki page for Rails integration
[Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)

### Configuration
Please check `configure` section for your controller

#### Signed assertions and Signed Response
## Configuration

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.
Your application need to manage 2 things for configuration.
1. IdP data such as IdP certificate and other configs
For this configuration you have to create global initialization file in your application
Which do following configuration.

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](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
```ruby
SamlIdP.config do
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
end
```

#### 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:
2. SP data such

```ruby
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 },
# },
# }
configure_sp 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

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]"

# 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]"
end
```

## 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](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)

## 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

## Keys and Secrets

To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
Expand Down Expand Up @@ -189,13 +202,13 @@ The second parameter is optional and default to your configuration `SamlIdp.conf
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.

## Author
# Author

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

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

## Copyright
# Copyright

Copyright (c) 2012 Sport Ngin.
Portions Copyright (c) 2010 OneLogin, LLC
Expand Down
14 changes: 5 additions & 9 deletions lib/saml_idp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,20 @@ module SamlIdp
require 'active_support/all'
require 'saml_idp/saml_response'
require 'saml_idp/xml_security'
require 'saml_idp/configurator'
require 'saml_idp/sp_config'
require 'saml_idp/controller'
require 'saml_idp/default'
require 'saml_idp/metadata_builder'
require 'saml_idp/version'
require 'saml_idp/fingerprint'
require 'saml_idp/engine' if defined?(::Rails)

def self.config
@config ||= SamlIdp::Configurator.new
end

def self.configure
yield config
def self.saml_idp_global_config
@saml_idp_global_config ||= OpenStruct.new(logger: ::Logger.new($stdout))
end

def self.metadata
@metadata ||= MetadataBuilder.new(config)
def self.config
yield saml_idp_global_config
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/saml_idp/algorithmable.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module SamlIdp
module Algorithmable
def algorithm
algorithm_check = raw_algorithm || SamlIdp.config.algorithm
algorithm_check = raw_algorithm
return algorithm_check if algorithm_check.respond_to?(:digest)
begin
OpenSSL::Digest.const_get(algorithm_check.to_s.upcase)
Expand Down
6 changes: 3 additions & 3 deletions lib/saml_idp/assertion_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ class AssertionBuilder
attr_accessor :session_expiry
attr_accessor :name_id_formats_opts
attr_accessor :asserted_attributes_opts

delegate :config, to: :SamlIdp
attr_accessor :config

def initialize(
sp_config,
reference_id,
issuer_uri,
principal,
Expand All @@ -42,7 +42,7 @@ def initialize(
self.audience_uri = audience_uri
self.saml_request_id = saml_request_id
self.saml_acs_url = saml_acs_url
self.raw_algorithm = raw_algorithm
self.raw_algorithm = raw_algorithm || sp_config.algorithm
self.authn_context_classref = authn_context_classref
self.expiry = expiry
self.encryption_opts = encryption_opts
Expand Down
13 changes: 13 additions & 0 deletions lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def encode_authn_response(principal, opts = {})
compress_opts = opts[:compress] || false

SamlResponse.new(
sp_config,
reference_id,
response_id,
opt_issuer_uri,
Expand Down Expand Up @@ -140,5 +141,17 @@ def get_saml_reference_id
def default_algorithm
OpenSSL::Digest::SHA256
end

def sp_config
@sp_config ||= SamlIdp::IdPConfig.new
end

def configure_sp
yield sp_config
end

def idp_metadata
@idp_metadata ||= MetadataBuilder.new(sp_config)
end
end
end
Loading

0 comments on commit 43a9b2d

Please sign in to comment.