Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some features from the PR #197 (PR splitted) #225

Merged
merged 4 commits into from
Apr 29, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
18 changes: 17 additions & 1 deletion lib/onelogin/ruby-saml/attribute_service.rb
Original file line number Diff line number Diff line change
@@ -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,18 +19,29 @@ 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
# @param name [String] The service name
#
def service_name(name)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not service_name= ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@name = name
end

# Set an index
# @param name [Integer] An index
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@param should describe index instead

#
def service_index(index)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not service_index= ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was only adding comments on this PR, the code on master is without the =.

Notice that the attribute is index, and we named the setter service_index.

@index = index
end


# Add an attribute
# @param name [Hash] Attribute for the AttributeService
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@param should describe options and if possible, please provide @options of the possible options that this method accepts

#
def add_attribute(options={})
attributes << options
end
Expand Down
65 changes: 42 additions & 23 deletions lib/onelogin/ruby-saml/attributes.rb
Original file line number Diff line number Diff line change
@@ -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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what will this Boolean represent?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line describe the behavior: Turn the backwards compatibility mode on/off.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kk, we can dismiss this one

#
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be @return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return [NilClass] If the value was not found

#
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it has to be always an array, wrap the response. Be careful:

1.8.7-head :001 > nil.to_a
 => []
1.8.7-head :002 > [].to_a
 => []
1.8.7-head :003 > {}.to_a
 => []
1.8.7-head :004 > {:a => 1, :b => 2}.to_a
 => [[:a, 1], [:b, 2]]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daniel-g PR for that is welcome since you are the Ruby Master ;)

#
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
Original file line number Diff line number Diff line change
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 parameters to build the request
# @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 parameters to build the request
# @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
Loading