Skip to content

Commit

Permalink
Merge pull request #225 from onelogin/document-code
Browse files Browse the repository at this point in the history
Some features from the PR #197 (PR splitted):
* Comment the code.
* Remove spaces and format some lines.
* Remove unnecessary errors method.
* Improve format_cert and format_private_key.
* Fix xpath injection on xml_security.rb
  • Loading branch information
pitbulk committed Apr 29, 2015
2 parents 1b3972a + 9c6ffdf commit 1b4e3dd
Show file tree
Hide file tree
Showing 17 changed files with 567 additions and 128 deletions.
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,4 +1,4 @@
Copyright (c) 2010 OneLogin, LLC
Copyright (c) 2010-2015 OneLogin, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -110,7 +110,7 @@ def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize"
settings.issuer = request.host
settings.issuer = "http://#{request.host}/saml/metadata"
settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
Expand Down Expand Up @@ -160,7 +160,7 @@ class SamlController < ApplicationController
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.issuer = request.host
settings.issuer = "http://#{request.host}/saml/metadata"
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
Expand Down Expand Up @@ -196,7 +196,7 @@ def saml_settings
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.issuer = request.host
settings.issuer = "http://#{request.host}/saml/metadata"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
# Optional for most SAML IdPs
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
Expand Down
1 change: 0 additions & 1 deletion changelog.md
Expand Up @@ -15,7 +15,6 @@
* [#179](https://github.com/onelogin/ruby-saml/pull/179) Add support for setting the entity ID and name ID format when parsing metadata
* [#175](https://github.com/onelogin/ruby-saml/pull/175) Introduce thread safety to SAML schema validation
* [#171](https://github.com/onelogin/ruby-saml/pull/171) Fix inconsistent results with using regex matches in decode_raw_saml
*

### 0.9.1 (Feb 10, 2015)
* [#194](https://github.com/onelogin/ruby-saml/pull/194) Relax nokogiri gem requirements
Expand Down
27 changes: 25 additions & 2 deletions lib/onelogin/ruby-saml/attribute_service.rb
@@ -1,10 +1,15 @@
module OneLogin
module RubySaml

# SAML2 AttributeService. Auxiliary class to build the AttributeService of the SP Metadata
#
class AttributeService
attr_reader :attributes
attr_reader :name
attr_reader :index

# Initializes the AttributeService, set the index value as 1 and an empty array as attributes
#
def initialize
@index = "1"
@attributes = []
Expand All @@ -14,20 +19,38 @@ def configure(&block)
instance_eval &block
end

# @return [Boolean] True if the AttributeService object has been initialized and set with the required values
# (has attributes and a name)
def configured?
@attributes.length > 0 && !@name.nil?
end

# Set a name to the service
# @param name [String] The service name
#
def service_name(name)
@name = name
end

# Set an index to the service
# @param index [Integer] An index
#
def service_index(index)
@index = index
end


# Add an AttributeService
# @param options [Hash] AttributeService option values
# add_attribute(
# :name => "Name",
# :name_format => "Name Format",
# :index => 1,
# :friendly_name => "Friendly Name",
# :attribute_value => "Attribute Value"
# )
#
def add_attribute(options={})
attributes << options
attributes << options
end
end
end
Expand Down
65 changes: 42 additions & 23 deletions lib/onelogin/ruby-saml/attributes.rb
@@ -1,91 +1,110 @@
module OneLogin
module RubySaml
# Wraps all attributes and provides means to query them for single or multiple values.
#
# For backwards compatibility Attributes#[] returns *first* value for the attribute.
# Turn off compatibility to make it return all values as an array:
# Attributes.single_value_compatibility = false

# SAML2 Attributes. Parse the Attributes from the AttributeStatement of the SAML Response.
#
class Attributes
include Enumerable

attr_reader :attributes

# By default Attributes#[] is backwards compatible and
# returns only the first value for the attribute
# Setting this to `false` returns all values for an attribute
@@single_value_compatibility = true

# Get current status of backwards compatibility mode.
# @return [Boolean] Get current status of backwards compatibility mode.
#
def self.single_value_compatibility
@@single_value_compatibility
end

# Sets the backwards compatibility mode on/off.
# @param value [Boolean]
#
def self.single_value_compatibility=(value)
@@single_value_compatibility = value
end

# Initialize Attributes collection, optionally taking a Hash of attribute names and values.
#
# The +attrs+ must be a Hash with attribute names as keys and **arrays** as values:
# @param attrs [Hash] The +attrs+ must be a Hash with attribute names as keys and **arrays** as values:
# Attributes.new({
# 'name' => ['value1', 'value2'],
# 'mail' => ['value1'],
# })
#
def initialize(attrs = {})
@attributes = attrs
end


# Iterate over all attributes
#
def each
attributes.each{|name, values| yield name, values}
end


# Test attribute presence by name
# @param name [String] The attribute name to be checked
#
def include?(name)
attributes.has_key?(canonize_name(name))
end

# Return first value for an attribute
# @param name [String] The attribute name
# @return [String] The value (First occurrence)
#
def single(name)
attributes[canonize_name(name)].first if include?(name)
end

# Return all values for an attribute
# @param name [String] The attribute name
# @return [Array] Values of the attribute
#
def multi(name)
attributes[canonize_name(name)]
end

# By default returns first value for an attribute.
#
# Depending on the single value compatibility status this returns first value
# Attributes.single_value_compatibility = true # Default
# response.attributes['mail'] # => 'user@example.com'
# Retrieve attribute value(s)
# @param name [String] The attribute name
# @return [String|Array] Depending on the single value compatibility status this returns:
# - First value if single_value_compatibility = true
# response.attributes['mail'] # => 'user@example.com'
# - All values if single_value_compatibility = false
# response.attributes['mail'] # => ['user@example.com','user@example.net']
#
# Or all values:
# Attributes.single_value_compatibility = false
# response.attributes['mail'] # => ['user@example.com','user@example.net']
def [](name)
self.class.single_value_compatibility ? single(canonize_name(name)) : multi(canonize_name(name))
end

# Return all attributes as an array
# @return [Array] Return all attributes as an array
#
def all
attributes
end

# Set values for an attribute, overwriting all existing values
# @param name [String] The attribute name
# @param values [Array] The values
#
def set(name, values)
attributes[canonize_name(name)] = values
end
alias_method :[]=, :set

# Add new attribute or new value(s) to an existing attribute
# @param name [String] The attribute name
# @param values [Array] The values
#
def add(name, values = [])
attributes[canonize_name(name)] ||= []
attributes[canonize_name(name)] += Array(values)
end

# Make comparable to another Attributes collection based on attributes
# @param other [Attributes] An Attributes object to compare with
# @return [Boolean] True if are contains the same attributes and values
#
def ==(other)
if other.is_a?(Attributes)
all == other.all
Expand All @@ -97,13 +116,13 @@ def ==(other)
protected

# stringifies all names so both 'email' and :email return the same result
# @param name [String] The attribute name
# @return [String] stringified name
#
def canonize_name(name)
name.to_s
end

def attributes
@attributes
end
end
end
end
30 changes: 26 additions & 4 deletions lib/onelogin/ruby-saml/authrequest.rb
Expand Up @@ -4,17 +4,30 @@
require "onelogin/ruby-saml/logging"
require "onelogin/ruby-saml/saml_message"

# Only supports SAML 2.0
module OneLogin
module RubySaml
include REXML

# SAML2 Authentication. AuthNRequest (SSO SP initiated, Builder)
#
class Authrequest < SamlMessage

attr_reader :uuid # Can be obtained if neccessary
# AuthNRequest ID
attr_reader :uuid

# Initializes the AuthNRequest. An Authrequest Object that is an extension of the SamlMessage class.
# Asigns an ID, a random uuid.
#
def initialize
@uuid = "_" + UUID.new.generate
end

# Creates the AuthNRequest string.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [String] AuthNRequest string that includes the SAMLRequest
#
def create(settings, params = {})
params = create_params(settings, params)
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
Expand All @@ -26,6 +39,11 @@ def create(settings, params = {})
@login_url = settings.idp_sso_target_url + request_params
end

# Creates the Get parameters for the request.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [Hash] Parameters
#
def create_params(settings, params={})
# The method expects :RelayState but sometimes we get 'RelayState' instead.
# Based on the HashWithIndifferentAccess value in Rails we could experience
Expand All @@ -49,7 +67,7 @@ def create_params(settings, params={})
url_string = "SAMLRequest=#{CGI.escape(base64_request)}"
url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
private_key = settings.get_sp_key()
private_key = settings.get_sp_key
signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string)
params['Signature'] = encode(signature)
end
Expand All @@ -61,6 +79,10 @@ def create_params(settings, params={})
request_params
end

# Creates the SAMLRequest String.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @return [String] The SAMLRequest String.
#
def create_authentication_xml_doc(settings)
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")

Expand Down Expand Up @@ -118,8 +140,8 @@ def create_authentication_xml_doc(settings)

# embed signature
if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
private_key = settings.get_sp_key()
cert = settings.get_sp_cert()
private_key = settings.get_sp_key
cert = settings.get_sp_cert
request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
end

Expand Down

0 comments on commit 1b4e3dd

Please sign in to comment.