POST Requests with Ruby Faraday OpenSSL PKCS12 - ruby

Good day!
I am trying to send POST requests to a server via ssl connection with .p12 certificate from Windows 7 with Ruby's Faraday library.
Ruby's version is ruby 2.3.3p222 (2016-11-21 revision 56859) [x64-mingw32]
Faraday gem's version are: faraday (0.14.0, 0.9.2)
1) I have a folder with the following cert files:
[cert_name].crt,
[cert_name].csr,
[cert_name].key,
[cert_name].p12
2) As to the code, I have the following:
require "faraday"
require "json"
require "openssl"
data = [JSON_object]
host = 'https://[domain_name]'
url = '[string]/[string]'
p12 = OpenSSL::PKCS12.new(File.open('[path_to_folder_with_cert_files]/[cert_name].p12', "rb").read, "[password]")
key = p12.key
cert = p12.certificate
connection = Faraday::Connection.new host, :ssl => {
:client_cert => cert,
:client_key => key,
:ca_file => '[path_to_folder_with_cert_files]/[cert_name].crt',
:verify => false
}
puts response.status = connection.post do |req|
req.url(url)
req.headers['Content-Type'] = #headers["content_type"]
req.body = data
end
Response has 403 Forbidden. I have tested with no ssl connection the data, url, host parameters and the status is 200 OK.
Please help, as I have found no tutorial / question to this particular use of Ruby's Faraday and OpenSSL::PKCS12

The following worked for me:
class Gateway
def call
connection.get do |req|
req.url(url)
req.headers['Content-Type'] = 'text/xml'
req.body = data
end
end
def connection
Faraday.new(ssl: ssl) do |builder|
builder.request :retry
builder.response(:logger) unless Rails.env.test?
builder.adapter :net_http
end
end
def ssl
{
client_key: client_key,
client_cert: client_cert,
ca_file: 'CA.crt' # optional
}
end
def url
'https://...'
end
def client_key
p12.key
end
def client_cert
p12.certificate
end
def p12
OpenSSL::PKCS12.new(p12_file.read, p12_password)
end
def p12_file
File.open('<path-to-p12-file>', 'rb')
end
def p12_password
'password' # if password protected
end
end
And the usage:
puts Gateway.new.call.body

Related

Facing issues while calling method of module(having object of class , contained in another ruby file)

I am newbie in ruby and working on making performance automation framework using using 'ruby-jmeter' gem (provided by flood.io)
I have made following files structure
Where payload.rb and request.rb contains my common utility methods . Which I am calling from test.rb
(test.rb would be going to be written by QA ppl)
Performance Automation Framework Structure
request.rb (lying under 'common' folder)
require 'ruby-jmeter' #(Consider any 3rd party gem )
require 'rubygems'
module RubyJmeter
class ExtendedDSL < DSL #'ruby-jmeter' class extending
def get_common_headers()
commonHeaderHashMap = {
'tns' => 'urn:',
'Content-Type' => 'text/xml; charset=utf-8',
'Accept-Language' => 'en-US,en;q=0.5',
'Accept-Encoding' => 'gzip, deflate'
}
return commonHeaderHashMap
end
end
end
class Request
def initialize(dsl)
#dsl = dsl
end
def soap_post_web_request(name,rawBody)
endPoint = '/SoapEndPoint'
post name: name, url: endPoint , raw_body: rawBody do
tempHeaderHashMap = get_common_headers.merge( {'SOAPAction' =>
'urn:'+name} )
finalHeaderArray = []
tempHeaderHashMap.each {|key, value|
localHashMap = Hash.new
localHashMap = {:name => key, :value => value}
finalHeaderArray << localHashMap
}
header finalHeaderArray
end # End of soapCall
end #end of soap_post_web_request
# Passes method calls through to the underlying DSL (ruby-jmeter).
def method_missing method, *args, &block
#dsl.__send__ method, *args, &block
end
end
wrappingclasses_under_single_module.rb (lying under 'common' folder)
require 'rubygems'
require 'ruby-jmeter'
require 'require_all'
require_all './'
module PerformanceAutomation
def self.MyRequest
Request.new(self)
end
end
test.rb (lying under 'testflow->simpleflow' folder)
require 'ruby-jmeter' #(Consider any 3rd party gem )
require 'rubygems'
require 'require_all' #(another 3rd party gem)
require_all '../../common/'
include PerformanceAutomation
test name:'JiraAnalyticsPerformanceFlow' do
threads name: 'NoOfUsers',scheduler: false,continue_forever: false,
count: 1 do
PerformanceAutomation.MyRequest.soap_post_web_request('soapRequestmethodName',rawdata)# End of Soap request 'soapRequestmethodName'
end # End of TestPlan
view_results_tree
puts "JMX FILE IS GONNA SAVED # "+Dir.pwd+"/CreatedJMeter_DB2Navigation.jmx"
end.jmx(file: Dir.pwd+"/CreatedJMeter_DB2Navigation.jmx")
When running test.rb , I am getting following error
`<top (required)>': uninitialized constant PerformanceAutomation (NameError)
Edit (It is working fine now)
Updated common utility files by using
request.rb
module RubyJmeter
class ExtendedDSL < DSL
def get_admin_common_headers()
commonHeaderHashMap = {
'X-XSRF-TOKEN' => '${COOKIE_XSRF-TOKEN}',
'Content-Type' => 'text/xml; charset=utf-8',
'Accept-Language' => 'en-US,en;q=0.5',
'Accept-Encoding' => 'gzip, deflate'
}
return commonHeaderHashMap
end
end
end
module API
class AdminWebService
def initialize(dsl)
#dsl = dsl
end
## Admin request for 'get_space_properties'
def get_space_properties(rawBody)
endPoint = "/AdminService.asmx"
post name: "admin_GetSpaceProperties", url: endPoint ,
raw_body:rawBody do
tempHeaderHashMap = get_admin_common_headers.merge( {'SOAPAction' =>
'http://example.com/GetSpaceProperties'} )
finalHeaderArray = []
tempHeaderHashMap.each {|key, value|
localHashMap = Hash.new
localHashMap = {:name => key, :value => value}
finalHeaderArray << localHashMap
}
header finalHeaderArray
end # End get_space_properties
end
test.rb
require 'rubygems'
require 'ruby-jmeter'
require 'require_all'
require_all '../../../common/'
defaults domain: 'example.com', protocol: 'http', connect_timeout:
'2000', response_timeout: '3000'
cookies policy: 'compatibility', clear_each_iteration: true
#'cookies' is method defined under 'ruby-jmeter'
cache clear_each_iteration: true # 'cache' is method defined under
'ruby-jmeter'
# starting testPlan 'JiraAnalyticsPerformanceFlow'
test name:"testFlowName" do
adminwebservice = API::AdminWebService.new(self)
adminwebservice.get_space_properties(Payload.get_local_payload("get_space_properties.xml"))
#Payload is another common utility class for fetching payload stuff
end
puts "JMX FILE IS GONNA SAVED #
"+Dir.pwd+"/CreatedJMeter_DB2Navigation.jmx"
end.jmx(file: Dir.pwd+"/CreatedJMeter_DB2Navigation.jmx")

Google API for Blogger 3.0 error

I am trying to run sample code in Ruby to fetch blog posts list using Google's APIs for Blogger 3.0. This is the code:
require 'rubygems'
require 'google/api_client'
require 'sinatra'
require 'google/api_client'
require 'logger'
enable :sessions
def logger; settings.logger end
def api_client; settings.api_client; end
def blogger_api; settings.blogger; end
def user_credentials
# Build a per-request oauth credential based on token stored in session
# which allows us to use a shared API client.
#authorization ||= (
auth = api_client.authorization.dup
auth.redirect_uri = to('/oauth2callback')
auth.update_token!(session)
auth
)
end
configure do
log_file = File.open('blogger.log', 'a+')
log_file.sync = true
logger = Logger.new(log_file)
logger.level = Logger::DEBUG
client = Google::APIClient.new
client.authorization.client_id = 'XXXXXXXXXXXXX'
client.authorization.client_secret = 'XXXXXXXXXXXXX'
client.authorization.scope = 'https://www.googleapis.com/auth/blogger'
blogger = client.discovered_api('blogger', 'v3')
set :logger, logger
set :api_client, client
set :blogger, blogger
end
before do
# Ensure user has authorized the app
unless user_credentials.access_token || request.path_info =~ /^\/oauth2/
redirect to('/oauth2authorize')
end
end
after do
# Serialize the access/refresh token to the session
session[:access_token] = user_credentials.access_token
session[:refresh_token] = user_credentials.refresh_token
session[:expires_in] = user_credentials.expires_in
session[:issued_at] = user_credentials.issued_at
end
get '/oauth2authorize' do
# Request authorization
redirect user_credentials.authorization_uri.to_s, 303
end
get '/oauth2callback' do
# Exchange token
user_credentials.code = params[:code] if params[:code]
user_credentials.fetch_access_token!
redirect to('/')
end
get '/' do
# Fetch list of posts
result = api_client.execute(:api_method => settings.blogger.posts.list, :parameters => {'blogId' => 'XXXXXXXXXXXXX'})
[result.status, {'Content-Type' => 'application/json'}, result.data.to_json]
end
When I connect to the running local application (after authorized it) I can see this "sinatra" error:
#<NameError: undefined local variable or method `blogger' for #<Sinatra::Application:0x92ac2dc>>
It is a bit obscure to me. Any idea?

OAuth2 gem: implementation for third party - access other accounts data in seek.com

I'm connecting to an API (seek.com.au) which uses OAuth2 for authentication. I struggled with OAuth2 gem for a while and I ended up writing the plain requests as will follow. Although this is working, I still would like to understand what was wrong with my initial OAuth2 implementation.
Here is my current working code, **the third party* relates to the fact that I'm accessing the API with an account that have access to other accounts. This logic is mainly implemented in the scope method (at the bottom of this snippet).
The following includes some extra logic, but the get_grant and post_for_token methods should include everything.
module Seek::Base
CONFIG = YAML.load_file "#{Rails.root}/config/seek.yml"
HOST = 'http://test.api.seek.com.au/v1/'
REQUEST_URIS = {
get_grant: HOST + 'OAuth/auth',
post_for_token: HOST + 'OAuth/token',
get_applications: HOST + 'advertiser/applications'
}
def uri_for(request, params = {})
uri = REQUEST_URIS[request]
uri += '?' + params.to_param if params.any?
URI.parse uri
end
end
class Seek::OAuth2 # TODO? is instance needed?
include Seek::Base
# by account_id
##tokens = {}
def initialize(account_id)
#account_id = account_id
end
def self.authenticate!(account_id)
new(account_id).authenticate!
end
# eg: when a request responded that the token is expired
def self.expire_token(account_id)
##tokens.delete account_id
end
###########################################################################
############################### begin #####################################
# authentication
# see: http://developer.seek.com.au/docs/partner-api/api-methods/oauth-2.0
def authenticate!
##tokens[#account_id] ||= begin
grant = get_grant
raise Exception.new(#error) if #error
Rails.logger.info "Retrive token for #{#account_id}"
post_for_token
end
end
private
# part of t he authentication process
# as we have one account for many entities, we use third party variation
# see: http://developer.seek.com.au/docs/partner-api/api-methods/oauth2/auth
def get_grant
uri = uri_for :get_grant, {response_type: :code, client_id: username, scope: scope}
response = Net::HTTP.get_response uri
params = response['location'].split('?').second
#error = params.split('error=').second
#grant_code = params.split('code=').second
end
# part of the authentication process
# see: http://developer.seek.com.au/docs/partner-api/api-methods/oauth2/token
def post_for_token
uri = uri_for :post_for_token
request = Net::HTTP::Post.new uri.path, {'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'}
request.set_form grant_type: :authorization_code, code: #grant_code, redirect_uri: ''
request.basic_auth username, password
response = Net::HTTP.new(uri.host, uri.port).request request
JSON(response.body)['access_token']
end
########################## end ############################################
###########################################################################
def username
CONFIG['credentials']['username']
end
def password
CONFIG['credentials']['password']
end
############## the scope method
############## I think I need to insert this in the OAuth request
def scope
"urn:seek:thirdparty:username:#{username},urn:seek:advertiser:identity:#{#account_id}"
end
end
And here are the few lines (to replace the authenticate! method) that were meant to do the same, but sadly, OAuth returns invalid_client.
client = OAuth2::Client.new(username, password, :site => 'http://test.api.seek.com.au/v1')
client.auth_code.authorize_url redirect_uri: ''
token = client.auth_code.get_token 'authorization_code_value',
headers: {'Authorization' => %^Basic #{Base64.encode64 "#{username}:#{password}"}^ }
I think the problem relies in the scope method created by OAuth (see bottom of the first snippet), but I'm not sure and anyways I couldn't find a way to amend it.
I also opened an issue in GitHub, but I think this is covered, just it's not documented (or I can't find it).
Ruby (Rails) implementation
This implementation is not using any wrapper, I tried the gem OAuth2 but I was not able to get the grant code,
I presume because the third party implementation require a customization of the scope which I was not able to set with the gem.
module Api::Base
CONFIG = YAML.load_file "#{Rails.root}/config/api.yml"
HOST = 'https://api.com.au/v1/'
REQUEST_URIS = {
get_grant: HOST + 'OAuth/auth',
post_for_token: HOST + 'OAuth/token',
get_applications: HOST + 'advertiser/applications'
}
def uri_for(request, params = {})
uri = REQUEST_URIS[request]
uri += '?' + params.to_param if params.any?
URI.parse uri
end
end
class Api::OAuth2
include Api::Base
# by account_id
##tokens = {}
def initialize(account_id)
#account_id = account_id
end
def self.authenticate!(account_id)
new(account_id).authenticate!
end
# eg: when a request responded that the token is expired
def self.expire_token(account_id)
##tokens.delete account_id
end
# authentication
def authenticate!
##tokens[#account_id] ||= begin
grant = get_grant
raise StandardError.new(#error) if #error
puts "Retrive token for #{#account_id}"
post_for_token
end
end
private
# part of t he authentication process
# as we have one account for many entities, we use third party variation
def get_grant
uri = uri_for :get_grant, {response_type: :code, client_id: username, scope: scope}
http = Net::HTTP.new uri.host, uri.port
http.use_ssl = uri.port == 443
puts "SSL not set for uri #{uri}" unless http.use_ssl?
response = http.get uri.to_s
raise Exception.new(response.message) unless response.is_a? Net::HTTPFound
params = response['location'].split('?').second
#error = params.split('error=').second
#grant_code = params.split('code=').second
end
# part of the authentication process
def post_for_token
uri = uri_for :post_for_token
request = Net::HTTP::Post.new uri.path, {'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'}
request.set_form grant_type: 'authorization_code', code: #grant_code, redirect_uri: ''
request.basic_auth username, password
http = Net::HTTP.new uri.host, uri.port
http.use_ssl = uri.port == 443
response = http.start {|http| http.request request}
JSON(response.body)['access_token']
end
end
def username
CONFIG['credentials']['username']
end
def password
CONFIG['credentials']['password']
end
def scope
"urn:api:thirdparty:username:#{username},urn:api:advertiser:identity:#{#account_id}"
end
end
I'm still planning to use OAuth 2, I'll post my updates here if any

em-http-request unexpected result when using tor as proxy

I've created a gist which shows exactly what happens.
https://gist.github.com/4418148
I've tested a version which used ruby's 'net/http' library and 'socksify/http' and it worked perfect but if the EventMachine version returns an unexpected result.
The response in Tor Browser is correct but using EventMachine is not!
It return a response but it's not the same as returned response when you send the request via browser, net/http with or without proxy.
For convenience, I will also paste it here.
require 'em-http-request'
DEL = '-'*40
#results = 0
def run_with_proxy
connection_opts = {:proxy => {:host => '127.0.0.1', :port => 9050, :type => :socks5}}
conn = EM::HttpRequest.new("http://www.apolista.de/tegernsee/kloster-apotheke", connection_opts)
http = conn.get
http.callback {
if http.response.include? "Oops"
puts "#{DEL}failed with proxy#{DEL}", http.response
else
puts "#{DEL}success with proxy#{DEL}", http.response
end
#results+=1
EM.stop_event_loop if #results == 2
}
end
def run_without_proxy
conn = EM::HttpRequest.new("http://www.apolista.de/tegernsee/kloster-apotheke")
http = conn.get
http.callback {
if http.response.include? "Oops"
puts "#{DEL}failed without proxy#{DEL}", http.response
else
puts "#{DEL}success without proxy#{DEL}", http.response
end
#results+=1
EM.stop_event_loop if #results == 2
}
end
EM.run do
run_with_proxy
run_without_proxy
end
Appreciate any clarification.

How to send mail with ruby over smtp with ssl (not with rails, no TLS for gmail)

All I want is to send emails from my ruby scripts, over SMTP using SSL.
I only find examples of doing it from Rails, or for Gmail with TLS.
I found people talking about SMTPS support with ruby 1.8.5, but the libdoc doesn't mention it.
Anyone with an example of sending mail over SMTP with SSL, on port 465?
ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
I solve this issue with this configuration below :
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:address => 'mail.domain.com',
:port => '465',
:domain => 'yourdomain.com',
:user_name => 'email#yourdomain.com',
:password => 'yourpassword',
:authentication => :login,
:ssl => true,
:openssl_verify_mode => 'none' #Use this because ssl is activated but we have no certificate installed. So clients need to confirm to use the untrusted url.
}
It works very well for me.
How about pony ?
gem install pony.
http://github.com/adamwiggins/pony/tree/master
or I did not understand your question ?
I hope it help you.
Thanks
tknv/
You probably already know about the Net::SMTP standard library
Regarding the SSL part, which doesn't seem to be supported out of the box, I found a couple of possible pointers:
Stephen Chu blog post
ActionMailer SSL setup example (ActionMailer uses Net::SMTP under the hood, AFIAK)
The only interesting thing I've heard of in programmatic email recently is Lamson: http://lamsonproject.org/
It's Python, not Ruby, but you can call Python from Ruby if you want to (here's one way: http://www.goto.info.waseda.ac.jp/~fukusima/ruby/python-e.html)
You could use a third party open source command line program like mailsend (http://www.muquit.com/muquit/software/mailsend/mailsend.html) to do your dirty work for you. Just pipe some output to it in the format it expects.
If you have to use SSL instead of TLS you can monkey-patch Net::SMTP like this:
require "openssl"
require "net/smtp"
Net::SMTP.class_eval do
def self.start( address, port = nil,
helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil, use_tls = false,
use_ssl = false, &block) # :yield: smtp
new(address, port).start(helo, user, secret, authtype, use_tls, use_ssl, &block)
end
def start( helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil, use_tls = false, use_ssl = false ) # :yield: smtp
start_method = use_tls ? :do_tls_start : use_ssl ? :do_ssl_start : :do_start
if block_given?
begin
send start_method, helo, user, secret, authtype
return yield(self)
ensure
do_finish
end
else
send start_method, helo, user, secret, authtype
return self
end
end
private
def do_tls_start(helodomain, user, secret, authtype)
raise IOError, 'SMTP session already started' if #started
if VERSION == '1.8.6'
check_auth_args user, secret, authtype if user or secret
elsif VERSION == '1.8.7'
check_auth_args user, secret
end
sock = timeout(#open_timeout) { TCPSocket.open(#address, #port) }
#socket = Net::InternetMessageIO.new(sock)
#socket.read_timeout = 60 ##read_timeout
#socket.debug_output = STDERR ##debug_output
check_response(critical { recv_response() })
do_helo(helodomain)
raise 'openssl library not installed' unless defined?(OpenSSL)
starttls
ssl = OpenSSL::SSL::SSLSocket.new(sock)
ssl.sync_close = true
ssl.connect
#socket = Net::InternetMessageIO.new(ssl)
#socket.read_timeout = 60 ##read_timeout
#socket.debug_output = STDERR ##debug_output
do_helo(helodomain)
authenticate user, secret, authtype if user
#started = true
ensure
unless #started
# authentication failed, cancel connection.
#socket.close if not #started and #socket and not #socket.closed?
#socket = nil
end
end
def do_ssl_start(helodomain, user, secret, authtype)
raise IOError, 'SMTP session already started' if #started
if VERSION == '1.8.6'
check_auth_args user, secret, authtype if user or secret
elsif VERSION == '1.8.7'
check_auth_args user, secret
end
sock = timeout(#open_timeout) { TCPSocket.open(#address, #port) }
raise 'openssl library not installed' unless defined?(OpenSSL)
ssl = OpenSSL::SSL::SSLSocket.new(sock)
ssl.sync_close = true
ssl.connect
#socket = Net::InternetMessageIO.new(ssl)
#socket.read_timeout = 60 ##read_timeout
#socket.debug_output = STDERR ##debug_output
check_response(critical { recv_response() })
do_helo(helodomain)
do_helo(helodomain)
authenticate user, secret, authtype if user
#started = true
ensure
unless #started
# authentication failed, cancel connection.
#socket.close if not #started and #socket and not #socket.closed?
#socket = nil
end
end
def do_helo(helodomain)
begin
if #esmtp
ehlo helodomain
else
helo helodomain
end
rescue Net::ProtocolError
if #esmtp
#esmtp = false
#error_occured = false
retry
end
raise
end
end
def starttls
getok('STARTTLS')
end
def quit
begin
getok('QUIT')
rescue EOFError, OpenSSL::SSL::SSLError
end
end
end
See http://github.com/collectiveidea/action_mailer_optional_tls/blob/master/lib/smtp_tls.rb

Resources