reward_station 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +2 -2
- data/README.md +121 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/award_points/award_points.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/award_points/award_points_invalid_token.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_point_summary/return_point_summary.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_point_summary/return_point_summary_invalid_token.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_popular_products/return_popular_products.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_popular_products/return_popular_products_invalid_token.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_token/return_token.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_token/return_token_invalid.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_user/return_user.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_user/return_user_invalid_token.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/return_user/return_user_invalid_user.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/update_user/create_user.xml +0 -0
- data/{spec/fixtures/savon/reward_station → lib/responses}/update_user/create_user_exists.xml +0 -0
- data/lib/reward_station.rb +10 -1
- data/lib/reward_station/errors.rb +31 -0
- data/lib/reward_station/service.rb +167 -0
- data/lib/reward_station/version.rb +1 -1
- data/lib/saml/auth_response.rb +174 -0
- data/lib/savon/macros.rb +12 -0
- data/lib/savon/mock.rb +70 -0
- data/lib/savon/mock_response.rb +45 -0
- data/reward_station.gemspec +5 -3
- data/spec/reward_station/service_spec.rb +326 -0
- data/spec/spec_helper.rb +1 -8
- metadata +44 -24
- data/lib/xceleration/reward_station.rb +0 -122
- data/spec/savon_helper.rb +0 -129
- data/spec/xceleration/reward_station_spec.rb +0 -348
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,126 @@
|
|
1
1
|
### Xceleration Reward Station
|
2
2
|
|
3
|
+
Client library for Xceleration rewardstation.com service
|
4
|
+
|
3
5
|
|
4
6
|
## Basic Usage
|
5
7
|
|
8
|
+
#Initialization
|
9
|
+
reward_station = RewardStation::Service.new :client_id => "112112", :client_password => "fsdftr#"
|
10
|
+
|
11
|
+
# Return Token
|
12
|
+
Request access token
|
13
|
+
|
14
|
+
token = reward_station.return_token
|
15
|
+
|
16
|
+
# Award Points
|
17
|
+
Update award points
|
18
|
+
|
19
|
+
user_id = "130"
|
20
|
+
points = 10
|
21
|
+
description = "Action 'Call to client' "
|
22
|
+
program_id = 90 # optional
|
23
|
+
point_reasond_code_id = 129 # optional
|
24
|
+
|
25
|
+
confirmation_number = reward_station.award_points user_id, points, description, program_id, point_reason_code_id
|
26
|
+
|
27
|
+
# Create User
|
28
|
+
|
29
|
+
user_attributes = reward_station.create_user :organization_id => '150',
|
30
|
+
:email => 'john5@company.com',
|
31
|
+
:first_name => 'John',
|
32
|
+
:last_name => 'Smith',
|
33
|
+
:user_name => 'john5@company.com',
|
34
|
+
:balance => 0
|
35
|
+
puts user_attributes.inspect
|
36
|
+
# {
|
37
|
+
# :user_id => '6727',
|
38
|
+
# :client_id => '100080',
|
39
|
+
# :user_name => 'john5@company.com',
|
40
|
+
# :email => 'john5@company.com',
|
41
|
+
# :encrypted_password => nil,
|
42
|
+
# :first_name => 'John',
|
43
|
+
# :last_name => 'Smith',
|
44
|
+
# :address_one => nil,
|
45
|
+
# :address_two => nil,
|
46
|
+
# :city => nil,
|
47
|
+
# :state_code => nil,
|
48
|
+
# :province => nil,
|
49
|
+
# :postal_code => nil,
|
50
|
+
# :country_code => 'USA',
|
51
|
+
# :phone => nil,
|
52
|
+
# :organization_id => '150',
|
53
|
+
# :organization_name => nil,
|
54
|
+
# :rep_type_id => '0',
|
55
|
+
# :client_region_id => '0',
|
56
|
+
#
|
57
|
+
# :is_active => true,
|
58
|
+
# :point_balance => '0',
|
59
|
+
# :manager_id => '0',
|
60
|
+
# :error_message => nil
|
61
|
+
# }
|
62
|
+
|
63
|
+
|
64
|
+
## Single-Sign-On
|
65
|
+
|
66
|
+
Basic SSO logic implemented in SAML::AuthResponse class. Example usage of AuthResponse:
|
67
|
+
|
68
|
+
#SessionController
|
69
|
+
|
70
|
+
require 'saml/auth_response'
|
71
|
+
|
72
|
+
class SessionController < ApplicationController
|
73
|
+
|
74
|
+
def create
|
75
|
+
@user_session = UserSession.new(params[:user_session])
|
76
|
+
if @user_session.save
|
77
|
+
if session[:saml_request].present?
|
78
|
+
sso_params(session[:saml_request], session[:relay_state], current_user)
|
79
|
+
session[:saml_request] = session[:relay_state] = nil
|
80
|
+
render :template => "session/signon"
|
81
|
+
return
|
82
|
+
...
|
83
|
+
end
|
84
|
+
...
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def signon
|
89
|
+
if current_user.present?
|
90
|
+
sso_params(params[:SAMLRequest], params[:RelayState], current_user)
|
91
|
+
|
92
|
+
render :template => "session/signon"
|
93
|
+
else
|
94
|
+
session[:saml_request] = params[:SAMLRequest]
|
95
|
+
session[:relay_state] = params[:RelayState]
|
96
|
+
redirect_to signin_url
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy
|
101
|
+
current_user_session.destroy
|
102
|
+
redirect_to signin_url
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def sso_params saml_request, relay_state, user
|
108
|
+
@saml_response = SAML::AuthResponse.new(saml_request).response_url(user.xceleration_id)
|
109
|
+
@relay_state = relay_state
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# signon.html.erb
|
114
|
+
|
115
|
+
<html>
|
116
|
+
<body>
|
117
|
+
<form id="sso_form" action="http://www6.rewardstation.net/sso/100080/AssertionService.aspx?binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" method="post">
|
118
|
+
<input type="hidden" name="SAMLResponse" value="<%= @saml_response %>"/>
|
119
|
+
<input type="hidden" name="RelayState" value="<%= @relay_state %>"/>
|
120
|
+
</form>
|
121
|
+
<script type="text/javascript">
|
122
|
+
document.getElementById('sso_form').submit();
|
123
|
+
</script>
|
124
|
+
</body>
|
125
|
+
</html>
|
126
|
+
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/{spec/fixtures/savon/reward_station → lib/responses}/return_token/return_token_invalid.xml
RENAMED
File without changes
|
File without changes
|
data/{spec/fixtures/savon/reward_station → lib/responses}/return_user/return_user_invalid_token.xml
RENAMED
File without changes
|
data/{spec/fixtures/savon/reward_station → lib/responses}/return_user/return_user_invalid_user.xml
RENAMED
File without changes
|
File without changes
|
data/{spec/fixtures/savon/reward_station → lib/responses}/update_user/create_user_exists.xml
RENAMED
File without changes
|
data/lib/reward_station.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module RewardStation
|
2
|
+
|
3
|
+
module NestingError
|
4
|
+
attr_reader :cause
|
5
|
+
|
6
|
+
def initialize message = nil, cause = $!
|
7
|
+
@cause = cause
|
8
|
+
super(message || cause && cause.message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_backtrace bt
|
12
|
+
if cause
|
13
|
+
cause.backtrace.reverse.each do |line|
|
14
|
+
bt.last == line ? bt.pop : break
|
15
|
+
end
|
16
|
+
bt << "cause: #{cause.class.name}: #{cause}"
|
17
|
+
bt.concat cause.backtrace
|
18
|
+
end
|
19
|
+
super bt
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class InvalidAccount < StandardError; end
|
24
|
+
class InvalidToken < StandardError; end
|
25
|
+
class InvalidUser < StandardError; end
|
26
|
+
class UserAlreadyExists < StandardError; end
|
27
|
+
|
28
|
+
class ConnectionError < StandardError
|
29
|
+
include NestingError
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module RewardStation
|
2
|
+
class Service
|
3
|
+
|
4
|
+
def initialize options = {}
|
5
|
+
[:client_id, :client_password].each do |arg|
|
6
|
+
raise ArgumentError, "Missing required option '#{arg}'" unless options.has_key? arg
|
7
|
+
end
|
8
|
+
|
9
|
+
@client_id = options[:client_id]
|
10
|
+
@client_password = options[:client_password]
|
11
|
+
@token = options[:token]
|
12
|
+
@organization_id = options[:organization_id]
|
13
|
+
|
14
|
+
@program_id = options[:program_id]
|
15
|
+
@point_reason_code_id = options[:point_reason_code_id]
|
16
|
+
|
17
|
+
if options[:new_token_callback]
|
18
|
+
raise ArgumentError, "new_token_callback option should be proc or lambda" unless options[:new_token_callback].is_a?(Proc)
|
19
|
+
@new_token_callback = options[:new_token_callback]
|
20
|
+
end
|
21
|
+
|
22
|
+
@mode = :default
|
23
|
+
if options[:mode]
|
24
|
+
raise ArgumentError, "supported modes :default and :mock" unless [:mock, :default].include?(options[:mode].to_sym)
|
25
|
+
@mode = options[:mode].to_sym
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_token_callback &block
|
30
|
+
@new_token_callback = block
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def client
|
35
|
+
@@client ||= Savon::Client.new do |wsdl|
|
36
|
+
wsdl.document = File.join(File.dirname(__FILE__), '..', 'wsdl', 'reward_services.xml')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger
|
41
|
+
@@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger
|
46
|
+
Service.logger
|
47
|
+
end
|
48
|
+
|
49
|
+
def return_token
|
50
|
+
result = request :return_token, :body => {
|
51
|
+
'AccountNumber' => @client_id,
|
52
|
+
'AccountCode' => @client_password
|
53
|
+
}
|
54
|
+
|
55
|
+
logger.info "xceleration token #{result[:token]}"
|
56
|
+
|
57
|
+
result[:token]
|
58
|
+
end
|
59
|
+
|
60
|
+
def return_user user_id
|
61
|
+
request_with_token(:return_user, :body => { 'UserID' => user_id} )[:user_profile]
|
62
|
+
end
|
63
|
+
|
64
|
+
def award_points user_id, points, description, program_id = nil, point_reason_code_id = nil
|
65
|
+
request_with_token(:award_points, :body => {
|
66
|
+
'UserID' => user_id,
|
67
|
+
'Points' => points,
|
68
|
+
'ProgramID' => program_id || @program_id,
|
69
|
+
'PointReasonCodeID' => point_reason_code_id || @point_reason_code_id,
|
70
|
+
'Description' => description
|
71
|
+
})[:confirmation_number]
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def return_point_summary user_id
|
76
|
+
request_with_token(:return_point_summary, :body => {
|
77
|
+
'clientId' => @client_id,
|
78
|
+
'userId' => user_id
|
79
|
+
})[:point_summary_collection][:point_summary]
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_user user_id, attrs = {}
|
83
|
+
|
84
|
+
organization_id = attrs[:organization_id] || @organization_id
|
85
|
+
email = attrs[:email] || ""
|
86
|
+
first_name = attrs[:first_name] || ""
|
87
|
+
last_name = attrs[:last_name] || ""
|
88
|
+
user_name = attrs[:user_name] || email
|
89
|
+
balance = attrs[:balance] || 0
|
90
|
+
|
91
|
+
request_with_token(:update_user , :body => {
|
92
|
+
'updateUser' => {
|
93
|
+
'UserID' => user_id,
|
94
|
+
'ClientID' => @client_id,
|
95
|
+
'UserName' => user_name,
|
96
|
+
'FirstName' => first_name,
|
97
|
+
'LastName' => last_name,
|
98
|
+
'CountryCode' => 'USA',
|
99
|
+
'Email' => email,
|
100
|
+
'IsActive' => true,
|
101
|
+
'PointBalance' => balance,
|
102
|
+
'OrganizationID' => organization_id
|
103
|
+
}
|
104
|
+
})[:update_user]
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def create_user attrs = {}
|
109
|
+
update_user -1, attrs
|
110
|
+
end
|
111
|
+
|
112
|
+
def return_popular_products user_id
|
113
|
+
request_with_token(:return_popular_products , :body => { 'userId' => user_id} )[:products][:product]
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
|
118
|
+
def mock?
|
119
|
+
@mode == :mock
|
120
|
+
end
|
121
|
+
|
122
|
+
def update_token
|
123
|
+
@token = return_token
|
124
|
+
@new_token_callback.call(@token) if @new_token_callback
|
125
|
+
end
|
126
|
+
|
127
|
+
def inject_token params = {}
|
128
|
+
(params[:body] ||= {})['Token'] = @token
|
129
|
+
end
|
130
|
+
|
131
|
+
def request_with_token method_name, params
|
132
|
+
update_token unless @token
|
133
|
+
inject_token params
|
134
|
+
request method_name, params
|
135
|
+
rescue InvalidToken
|
136
|
+
update_token
|
137
|
+
inject_token params
|
138
|
+
request method_name, params
|
139
|
+
end
|
140
|
+
|
141
|
+
def request method_name, params
|
142
|
+
|
143
|
+
if mock?
|
144
|
+
#TODO
|
145
|
+
end
|
146
|
+
|
147
|
+
response = Service.client.request(:wsdl, method_name , params).to_hash
|
148
|
+
|
149
|
+
logger.debug response.inspect
|
150
|
+
|
151
|
+
result = response[:"#{method_name}_response"][:"#{method_name}_result"]
|
152
|
+
|
153
|
+
unless (error_message = result.delete(:error_message).to_s).nil?
|
154
|
+
raise InvalidToken if error_message.start_with?("Invalid Token")
|
155
|
+
raise InvalidAccount if error_message.start_with?("Invalid Account Number")
|
156
|
+
raise InvalidUser if error_message.start_with?("Invalid User")
|
157
|
+
raise UserAlreadyExists if error_message.start_with?("User Name:") && error_message.end_with?("Please enter a different user name.")
|
158
|
+
end
|
159
|
+
|
160
|
+
result
|
161
|
+
rescue Savon::SOAP::Fault, Savon::HTTP::Error => ex
|
162
|
+
logger.error ex.to_s
|
163
|
+
logger.error ex.backtrace.inspect
|
164
|
+
raise ConnectionError.new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module SAML
|
2
|
+
class AuthResponse
|
3
|
+
|
4
|
+
attr_accessor :request, :document, :logger
|
5
|
+
include Onelogin::Saml::Codeing
|
6
|
+
|
7
|
+
def initialize(request, logger = nil)
|
8
|
+
raise ArgumentError.new("Response cannot be nil") if request.nil?
|
9
|
+
self.logger = logger || ActiveRecord::Base.logger
|
10
|
+
self.request = inflate(decode(request))
|
11
|
+
self.document = Nokogiri::XML(self.request)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_sp_response_to
|
15
|
+
document.xpath("//samlp:AuthnRequest").first["ID"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_sp_destination
|
19
|
+
Settings.sso.sp_destination
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_idp_issuer
|
23
|
+
Settings.host
|
24
|
+
end
|
25
|
+
|
26
|
+
# def get_sp_audience
|
27
|
+
# Settings.sso.sp_audience
|
28
|
+
# end
|
29
|
+
|
30
|
+
|
31
|
+
#
|
32
|
+
def xml_time_format time
|
33
|
+
time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def response_url(name_id, params={})
|
38
|
+
prepared_result = create(name_id)
|
39
|
+
base64_request = encode(prepared_result)
|
40
|
+
# deflated_request = deflate(create(name_id))
|
41
|
+
# base64_request = encode(deflated_request)
|
42
|
+
base64_request.gsub!(/\s/,"")
|
43
|
+
# encoded_response = escape(base64_request)
|
44
|
+
#
|
45
|
+
# encoded_response
|
46
|
+
## request_params = "?SAMLResponse=" + encoded_response
|
47
|
+
#
|
48
|
+
# params.each_pair do |key, value|
|
49
|
+
# request_params << "&#{key}=#{escape(value.to_s)}"
|
50
|
+
# end
|
51
|
+
# Settings.sso.sp_destination+ request_params
|
52
|
+
end
|
53
|
+
|
54
|
+
def create(name_id)
|
55
|
+
time_line = Time.now.utc
|
56
|
+
request_id = UUID.new.generate
|
57
|
+
assert_id = UUID.new.generate
|
58
|
+
|
59
|
+
time = xml_time_format(time_line)
|
60
|
+
assertion = build_assertion_content(name_id, assert_id, time_line)
|
61
|
+
|
62
|
+
# assertion_parsed = Nokogiri::XML(assertion)
|
63
|
+
# assertion_parsed.xpath("//saml:Assertion//saml:Issuer").first.add_next_sibling(make_signature(assertion, assert_id))
|
64
|
+
|
65
|
+
response = Builder::XmlMarkup.new
|
66
|
+
response.tag!('samlp:Response', {"xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
67
|
+
"xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
|
68
|
+
"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
|
69
|
+
"ID" => request_id,
|
70
|
+
"Version" => "2.0",
|
71
|
+
"InResponseTo" => get_sp_response_to,
|
72
|
+
"IssueInstant" => time,
|
73
|
+
"Destination" => get_sp_destination}) do
|
74
|
+
response.tag!("saml:Issuer", get_idp_issuer)
|
75
|
+
response.tag!("samlp:Status") do
|
76
|
+
response.tag!("samlp:StatusCode", "Value" => "urn:oasis:names:tc:SAML:2.0:status:Success")
|
77
|
+
end
|
78
|
+
response << build_assertion_content(name_id, assert_id, time_line, make_signature(assertion, assert_id))
|
79
|
+
#todo if insert node this way - need to canonicalize. nokogiri 1.4.4 has no possibility to do it. only it's branch can do it:(
|
80
|
+
# response << assertion_parsed.xpath("//saml:Assertion").canonicalize.to_s
|
81
|
+
end
|
82
|
+
response.target!
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def build_assertion_content(name_id, assert_id, time_line, sign =nil)
|
87
|
+
time_and_ten = xml_time_format(time_line + 10.minutes)
|
88
|
+
time = xml_time_format(time_line)
|
89
|
+
|
90
|
+
xml = Builder::XmlMarkup.new
|
91
|
+
|
92
|
+
xml.tag!('saml:Assertion', {"xmlns:saml"=>"urn:oasis:names:tc:SAML:2.0:assertion", "ID"=>assert_id, "Version"=>"2.0", "IssueInstant"=> time}) do
|
93
|
+
xml.tag!("saml:Issuer", get_idp_issuer)
|
94
|
+
|
95
|
+
xml << sign if sign.present?
|
96
|
+
|
97
|
+
xml.tag! "saml:Subject" do
|
98
|
+
xml.tag!("saml:NameID", {"Format"=>"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"}, name_id)
|
99
|
+
xml.tag!("saml:SubjectConfirmation", {"Method" => "urn:oasis:names:tc:SAML:2.0:cm:bearer"}) do
|
100
|
+
xml.tag!("saml:SubjectConfirmationData", {"InResponseTo" => get_sp_response_to, "Recipient" => get_sp_destination, "NotOnOrAfter" => time_and_ten})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# xml.tag!("saml:Conditions", {"NotBefore" => time, "NotOnOrAfter" => time_and_ten}) do
|
105
|
+
# xml.tag!("saml:AudienceRestriction") do
|
106
|
+
# xml.tag!("saml:Audience", get_sp_audience)
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
|
110
|
+
xml.tag!("saml:AuthnStatement", {"AuthnInstant" => time, "SessionIndex" => assert_id}) do
|
111
|
+
xml.tag!("saml:AuthnContext") do
|
112
|
+
xml.tag!("saml:AuthnContextClassRef", "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
xml.target!
|
117
|
+
end
|
118
|
+
|
119
|
+
def make_signature(assertion, assert_id)
|
120
|
+
certificate = File.read("#{Rails.root}/config/cert/#{Rails.env}/idp.ignite.com.crt")
|
121
|
+
# certificate = File.read("#{Rails.root}/config/cert/development/IdpCertificate.cer")
|
122
|
+
certificate.gsub!("-----BEGIN CERTIFICATE-----", "")
|
123
|
+
certificate.gsub!("-----END CERTIFICATE-----", "")
|
124
|
+
|
125
|
+
sign_info_xml_builder = Builder::XmlMarkup.new
|
126
|
+
sign_info_xml_builder.tag!("ds:SignedInfo", {"xmlns:ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do
|
127
|
+
sign_info_xml_builder.tag!("ds:CanonicalizationMethod", {"Algorithm"=>"http://www.w3.org/2001/10/xml-exc-c14n#"})
|
128
|
+
sign_info_xml_builder.tag!("ds:SignatureMethod", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#rsa-sha1"})
|
129
|
+
sign_info_xml_builder.tag!("ds:Reference", {"URI" => "##{assert_id}"}) do
|
130
|
+
sign_info_xml_builder.tag!("ds:Transforms") do
|
131
|
+
sign_info_xml_builder.tag!("ds:Transform", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#enveloped-signature"})
|
132
|
+
sign_info_xml_builder.tag!("ds:Transform", {"Algorithm"=>"http://www.w3.org/2001/10/xml-exc-c14n#"})
|
133
|
+
end
|
134
|
+
sign_info_xml_builder.tag!("ds:DigestMethod", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#sha1"})
|
135
|
+
sign_info_xml_builder.tag!("ds:DigestValue", {"URI" => assert_id}, signature_digest_value(assertion))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
sign_info_xml = sign_info_xml_builder.target!
|
139
|
+
s_value = signature_sign_value(sign_info_xml)
|
140
|
+
xml = Builder::XmlMarkup.new
|
141
|
+
xml.tag!('ds:Signature', {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}) do
|
142
|
+
xml << sign_info_xml
|
143
|
+
xml.tag!("ds:SignatureValue", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}, s_value)
|
144
|
+
xml.tag!('ds:KeyInfo') do
|
145
|
+
xml.tag!('ds:X509Data') do
|
146
|
+
xml.tag!('ds:X509Certificate', certificate)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
xml.target!
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def signature_sign_value(xml)
|
155
|
+
document = XMLSecurity::SignedDocument.new(xml)
|
156
|
+
canoner = XML::Util::XmlCanonicalizer.new(false, true)
|
157
|
+
signed_info_element = REXML::XPath.first(document, "//ds:SignedInfo", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"})
|
158
|
+
canon_string = canoner.canonicalize(signed_info_element)
|
159
|
+
# private_key = OpenSSL::PKey::RSA.new(File.read("#{Rails.root}/config/cert/#{Rails.env}/idp.ignite.com.key"))
|
160
|
+
#private_key = OpenSSL::PKey::RSA.new(File.read("#{Rails.root}/config/cert/development/SPkey.key"))
|
161
|
+
private_key = OpenSSL::PKey::RSA.new(File.read("#{Rails.root}/config/cert/development/IgniteKeyDecrypted.key"))
|
162
|
+
|
163
|
+
sig = private_key.sign(OpenSSL::Digest::SHA1.new, canon_string)
|
164
|
+
Base64.encode64(sig).chomp
|
165
|
+
end
|
166
|
+
|
167
|
+
def signature_digest_value(xml)
|
168
|
+
document = XMLSecurity::SignedDocument.new(xml)
|
169
|
+
canoner = XML::Util::XmlCanonicalizer.new(false, true)
|
170
|
+
canon_hashed_element = canoner.canonicalize(document)
|
171
|
+
Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|