Generate a valid Authorization header for azure service bus/eventhubs - ruby

I am trying to use SAS authenticaion in a ruby script and i keep getting 401 (Access denied) response from the event hub, it seems I am generating the SAS token incorrectly.
Below is the code I have used, it is based on https://azure.microsoft.com/en-us/documentation/articles/service-bus-sas-overview/ Javascript example that i have rewritten as ruby (please note it might be not idiomatic)
require "optparse"
require "CGI"
require 'openssl'
require "base64"
require "Faraday"
require 'Digest'
def generateToken(url,keyname,keyvalue)
encoded = CGI::escape(url)
ttl = (Time.now + 60*5).to_i
signature = "#{encoded}\n#{ttl}".encode('utf-8')
# puts signature
key = Base64.strict_decode64(keyvalue)
dig = OpenSSL::HMAC.digest('sha256', key, signature)
# dig = Digest::HMAC.digest(signature, key, Digest::SHA256)
hash = CGI.escape(Base64.strict_encode64(dig))
# puts hash
return "SharedAccessSignature sig=#{hash}&se=#{ttl}&skn=#{keyname}&sr=#{encoded}"
end
def build_connection(url,token)
conn = Faraday.new(:url => url) do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
conn.headers['Content-Type'] = 'application/json'
conn.headers['Authorization'] = token
return conn
end
if __FILE__ == $0
ARGV << '-h' if ARGV.empty?
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: generateSasToken.rb [options]"
opts.on('-u URL', '--url URL', 'url for access') { |v| options[:url] = v }
opts.on('--keyname NAME','set key name') { |v| options[:keyname] = v }
opts.on('--key KEY','set key value') { |v| options[:keyvalue] = v }
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end.parse!
token = generateToken(options[:url],options[:keyname],options[:keyvalue])
puts token
conn = build_connection(options[:url],token)
puts conn.headers
response = conn.post do |req|
req.body = '{"temprature":50}'
req.headers['content-length'] = req.body.length.to_s
end
puts response
end
any help in understanding why the token is incorrect would be great

After comparing my code against the python sdk this is the correct way to generate the token:
require "optparse"
require "CGI"
require 'openssl'
require "base64"
require "Faraday"
require 'Digest'
def generateToken(url,keyname,keyvalue)
encoded = CGI::escape(url)
ttl = (Time.now + 60*5).to_i
signature = "#{encoded}\n#{ttl}"
# puts signature
key = keyvalue
#dig = OpenSSL::HMAC.digest('sha256', key, signature)
dig = Digest::HMAC.digest(signature, key, Digest::SHA256)
hash = CGI.escape(Base64.strict_encode64(dig))
# puts hash
return "SharedAccessSignature sig=#{hash}&se=#{ttl}&skn=#{keyname}&sr=#{encoded}"
end
def build_connection(url,token)
conn = Faraday.new(:url => url) do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
conn.headers['Content-Type'] = 'application/json'
conn.headers['Authorization'] = token
return conn
end
if __FILE__ == $0
ARGV << '-h' if ARGV.empty?
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: generateSasToken.rb [options]"
opts.on('-u URL', '--url URL', 'url for access') { |v| options[:url] = v }
opts.on('--keyname NAME','set key name') { |v| options[:keyname] = v }
opts.on('--key KEY','set key value') { |v| options[:keyvalue] = v }
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end.parse!
token = generateToken(options[:url],options[:keyname],options[:keyvalue])
conn = build_connection(options[:url],token)
puts conn.headers
response = conn.post do |req|
req.body = '{"temprature":50}'
req.headers['content-length'] = req.body.length.to_s
end
puts response
end
this was much simpler than expected no need to encode to utf8 or to decode the key.

Related

uninitialized constant Net::HTTPS (NameError)

I am trying to pull campaign stats from Clickbank API in ruby. When I run the sample code Clickbank provided. I get the following error:
uninitialized constant Net::HTTPS (NameError). What am I missing?
Example Code.
require 'net/http'
require 'net/https'
http = Net::HTTPS.new('api.clickbank.com')
http.use_ssl = false
path = '/rest/1.3/orders/list'
headers = {
'Authorization' => '<< DEVKEY >>:<< APIKEY>>',
'Accept' => 'application/json'
}
resp, data = http.get(path, nil, headers)
puts 'Code = ' + resp.code
puts 'Message = ' + resp.message
resp.each {|key, val| puts key + ' = ' + val}
puts data
Yes I put my dev and api key into
In Ruby 2.4.1 enable ssl as a parameter of Net::HTTP.start
Net::HTTP.start(uri.host, uri.port, use_ssl: true)
https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-HTTPS
Use Net:HTTP and enable SSL instead of using Net::HTTPS and disabling SSL.
Example:
http = Net::HTTP.new('api.clickbank.com')
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
You actually don't want to disable ssl as that API requires it. I was able to get it working like so based on the documentation for http found here: http://ruby-doc.org/stdlib-2.1.1/libdoc/net/http/rdoc/Net/HTTP.html
require 'net/http'
uri = URI('https://api.clickbank.com/rest/1.3/orders/list')
req = Net::HTTP::Get.new(uri)
# set headers on the request
req['Authorization'] = '<< DEVKEY >>:<< APIKEY>>'
req['Accept'] = 'application/json'
# perform the request
resp, data = Net::HTTP.start(uri.hostname, uri.port) {|http|
http.request(req)
}
puts 'Code = ' + resp.code
puts 'Message = ' + resp.message
resp.each {|key, val| puts key + ' = ' + val}
puts data

How to get response OK from POP3 server using ruby

For example, to get response 200 OK from "example.com", necessary:
require 'net/http'
uri = URI('http://example.com/index.html')
res = Net::HTTP.get_response(uri)
puts res.code # => '200'
puts res.message # => 'OK'
How to make similar for pop.gmail.com?
Try this:
require "net/pop"
Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_NONE)
conn = Net::POP3.new("pop.gmail.com", 995)
conn.start(user_name, password)
conn.started?

String interpolation and URI.parse error?

I have four arguments taken from user input in the script. All other string interpolation arguments work fine except for URI.parse.
Snippets from the code are:
require 'net/http'
#ARGVs:
prompt = 'test: '
url = ARGV[0]
user = ARGV[1]
pass = ARGV[2]
xml_user = ARGV[3]
# User supplied input:
puts "Whats the URL?"
print prompt
url = STDIN.gets.chomp()
# HTTP connection
uri = URI.parse('#{url}')
req = Net::HTTP.new(uri.hostname, uri.port)
# Header: Creds to get a session
user_and_pass = "#{user}" + ':' + "#{pass}"
base64user_and_pass = Base64.encode64(user_and_pass)
# POST method
res = req.post(uri.path, xml_data, {'Content-Type' => 'text/xml', 'Content-Length' => xml_data.length.to_s,
'Authorization' => "Basic #{base64user_and_pass}", "Connection" => "keep-alive" })
puts res.body
Error:
Ruby200-x64/lib/ruby/2.0.0/uri/common.rb:176:in `split': bad URI(is not URI?): #{url} (URI::InvalidURIError)
A point in the right direction would be appreciated.
uri = URI.parse('#{url}')
should be:
uri = URI.parse(url)
Here's a bit more idiomatic-Ruby version of the code:
require 'net/http'
PROMPT = 'test: '
# ARGVs:
url, user, pass, xml_user = ARGV[0, 4]
# User supplied input:
puts "Whats the URL?"
print PROMPT
url = STDIN.gets.chomp()
# HTTP connection
uri = URI.parse(url)
req = Net::HTTP.new(uri.hostname, uri.port)
# Header: Creds to get a session
base64user_and_pass = Base64.encode64("#{ user }:#{ pass }")
# POST method
res = req.post(
uri.path,
xml_data,
{
'Content-Type' => 'text/xml',
'Content-Length' => xml_data.length.to_s,
'Authorization' => "Basic #{ base64user_and_pass }",
"Connection" => "keep-alive"
}
)
puts res.body
Don't do things like:
user_and_pass = "#{user}" + ':' + "#{pass}"
and:
uri = URI.parse('#{url}')
user, pass and url are already strings, so sticking them inside a string and interpolating their values is a waste of CPU. As developers we need to be aware of our data-types.
It could be written as one of these:
user_and_pass = user + ':' + pass
user_and_pass = '%s:%s' % [user, pass]
user_and_pass = [user, pass].join(':')
But it's more idiomatic to see it how I wrote it above.

How to conditionally select one of two methods to receive same block

Im trying to change the process of an code dependent on an variable req how you can see here:
#res = #conn.post do |request| if req == 'post'
#res = #conn.get do |request| if req == 'get'
The problem is that this seems to raise an error:
stack.rb:89: syntax error, unexpected end-of-input, expecting keyword_end
user2.send_csr
My question is, what do i have to change to avoid this problem? If you need more information about my code:
def send(req,ww,text1=nil,text2=nil)
#conn = Faraday.new 'https://zombo.de/rest', :ssl => {:verify => false}
#conn.basic_auth(#username,#password)
#res = #conn.post do |request| if req == 'post'
#res = #conn.get do |request| if req == 'get'
request.url ww
request.headers['Content-Type'] = text1 unless text1 == nil
request.body = text2 unless text2 == nil
end
puts #res.body
end
def send_csr
send('post','csr','text/plain',"#{File.read(#csr[0..-5])}")
end
user2.send_csr
What if you extend your code a bit? Add some formating and change what goes into to blocks?
def send(req, ww, text1=nil, text2=nil)
#conn = Faraday.new 'https://zombo.de/rest', :ssl => {:verify => false}
#conn.basic_auth(#username,#password)
#res = #conn.post { |request| handle_request(request) } if req == 'post'
#res = #conn.get { |request| handle_request(request) } if req == 'get'
#res.body
end
def handle_request request
request.url ww
request.headers['Content-Type'] = text1 unless text1 == nil
request.body = text2 unless text2 == nil
request
end
def send_csr
send('post','csr','text/plain',"#{File.read(#csr[0..-5])}")
end
user2.send_csr
The post-fix if cannot be placed as you have it, because technically that is in the middle of the block that you want to pass to the get or post.
You could do this:
#res = #conn.get do |request|
request.url ww
request.headers['Content-Type'] = text1 unless text1 == nil
request.body = text2 unless text2 == nil
end if req == 'get'
but that would require you to repeat the code block for each case. Also, I'd recommend against post-fixing conditionals after a long block, it is difficult to spot them when reading the code later.
So this syntax, using send might work best for you (it works because your string matches the method name)
#conn.send(req) do |request|
request.url ww
request.headers['Content-Type'] = text1 unless text1 == nil
request.body = text2 unless text2 == nil
end
Faraday's post and get methods call run_request:
run_request(method, url, body, headers)
You could do the same:
def send(req, ww, text1=nil, text2=nil)
#conn = Faraday.new 'https://zombo.de/rest', :ssl => {:verify => false}
#conn.basic_auth(#username, #password)
headers = text1 && {'Content-Type' => text1 }
#res = #conn.run_request(req.to_sym, ww, text2, headers)
puts #res.body
end
I'm passing req.to_sym because run_request expects a symbol (:post instead of "post") and instead of setting url, body and headers in the block, I'm passing them, too.
Maybe you should rename some of your variables and replace instance variables by local ones:
def send(method, url, content_type=nil, body=nil)
conn = Faraday.new 'https://zombo.de/rest', :ssl => {:verify => false}
conn.basic_auth(#username, #password)
headers = content_type && {'Content-Type' => content_type }
res = conn.run_request(method.to_sym, url, body, headers)
puts res.body
end

How do I parse and format date data?

I wrote code to display tweets from a public account on Twitter:
require 'rubygems'
require 'oauth'
require 'json'
# Now you will fetch /1.1/statuses/user_timeline.json,
# returns a list of public Tweets from the specified
# account.
baseurl = "https://api.twitter.com"
path = "/1.1/statuses/user_timeline.json"
query = URI.encode_www_form(
"screen_name" => "CVecchioFX",
"count" => 10,
)
address = URI("#{baseurl}#{path}?#{query}")
request = Net::HTTP::Get.new address.request_uri
# Print data about a list of Tweets
def print_timeline(tweets)
# ADD CODE TO ITERATE THROUGH EACH TWEET AND PRINT ITS TEXT
tweets.each do |tweet|
puts "#{tweet["user"]["name"]} , #{tweet["text"]} , #{tweet["created_at"]} , # {tweet["id"]}"
end
end
# Set up HTTP.
http = Net::HTTP.new address.host, address.port
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# If you entered your credentials in the first
# exercise, no need to enter them again here. The
# ||= operator will only assign these values if
# they are not already set.
consumer_key = OAuth::Consumer.new(
)
access_token = OAuth::Token.new(
)
# Issue the request.
request.oauth! http, consumer_key, access_token
http.start
response = http.request request
# Parse and print the Tweet if the response code was 200
tweets = nil
if response.code == '200' then
tweets = JSON.parse(response.body)
print_timeline(tweets)
end
nil
The date is coming out as "Tue Jun 11 15:35:31 +0000 2013". What do I do to parse through the date and change it to a format such as "06.11.2013"?
Use Ruby's standard library Date:
require 'date'
d = DateTime.parse('Tue Jun 11 15:35:31 +0000 2013')
puts d.strftime('%m.%d.%y')
In your code, just update print_timeline method:
def print_timeline(tweets)
tweets.each do |tweet|
d = DateTime.new(tweet['created_at'])
puts "#{tweet['user']['name']} , #{tweet['text']} , #{d.strftime('%m.%d.%y')} , #{tweet['id']}"
end
end

Resources