I have this basic piece of code which doesn't work properly.
If I run the following I get:
".rvm/gems/ruby-1.9.3-p484/gems/multi_xml-0.5.5/lib/multi_xml/parsers/rexml.rb:18:in `parse': The document "The supplied authentication is invalid" does not have a valid root (MultiXml::ParseError)"
class Temp
include HTTParty
format :xml
def initialize()
self.class.base_uri "https://beta2014.vchs.vmware.com"
end
def login(username, password)
self.class.basic_auth username, password
self.class.default_options[:headers] = {"Accept" => "application/xml;version=5.7"}
response = self.class.post('/api/iam/login')
# setting global cookie var to be used later on
#cookie = response.headers['vchs-authorization']
return #cookie
end
def plans(token)
token = 'Bearer ' + token
self.class.base_uri "https://beta2014.vchs.vmware.com"
self.class.default_options[:headers] = { 'Accept' => "application/xml;class=com.vmware.vchs.iam.api.schema.v2.classes.user.Users;version=5.7", 'Authorization' => token }
response = self.class.get('/api/sc/plans')
end
end #class Temp
temp = Temp.new()
token = temp.login(username, password)
temp.plans(token)
The token has the right content. I know that because if I "puts" the value of the token and copy&paste it into a REST client it works just fine.
Interestingly enough, if I generate the token on the REST client and I statically set the token with token = <string> (instead of calling temp.login) the call temp.plans works just fine and I get my proper response.
I have also tried to do:
tokendummy = temp.login(username, password)
token = <string>
and it fails again.
It's like if token.plans works only if it's the first method being called. If it's the second (after temp.login) it fails.
Does it have something to do with the initialize() method that is only being called the first time?
Related
Now I took a sample code of Twitter v2 API from this link. This sample code shows how OAuth and twitter v2 API work for positng a tweet. It works fine with my consumer key and consumer secret.
And I want to simplify the code like below. It assumes that the access token and access token secret are already known and it skips the process of user's approval, like providing the URL that provides PIN.
require 'typhoeus'
require 'json'
consumer_key = CONSUMER_KEY
consumer_secret = CONSUMER_SECRET
token = ACCESS_TOKEN
token_secret = ACCESS_TOKEN_SECRET
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, :site => 'https://api.twitter.com')
options = {
:method => :post,
headers: {
"User-Agent": "v2CreateTweetRuby",
"content-type": "application/json"
},
body: JSON.dump("Hello, world!")
}
create_tweet_url = "https://api.twitter.com/2/tweets"
request = Typhoeus::Request.new(create_tweet_url, options)
access_token = OAuth::Token.new(token, token_secret)
oauth_params = {:consumer => consumer, :token => access_token}
oauth_helper = OAuth::Client::Helper.new(request, oauth_params.merge(:request_uri => create_tweet_url))
request.options[:headers].merge!({"Authorization" => oauth_helper.header}) # Signs the request
response = request.run
puts response
Then, I see the below error message.
ruby test_tweet.rb
/usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/request_proxy.rb:18:in `proxy': Typhoeus::Request (OAuth::RequestProxy::UnknownRequestType)
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/signature.rb:12:in `build'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/signature.rb:23:in `sign'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/client/helper.rb:49:in `signature'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/client/helper.rb:82:in `header'
from test_tweet.rb:28:in `<main>'
When I used irb and tried step by step, this error happens at oauth_helper.header. As this is the first time to use OAuth API, I may be making some easy mistakes. Does anybody find anything wrong in my code?
I confirmed that my access token and access token secret work at https://web.postman.co/.
Thanks.
You need to insert
require 'oauth/request_proxy/typhoeus_request'
and your code may complete task you desire.
Other lines looks good to me!
In oauth/request_proxy.rb, oauth library check class of request object.
https://github.com/oauth-xx/oauth-ruby/blob/master/lib/oauth/request_proxy.rb
return request if request.is_a?(OAuth::RequestProxy::Base)
klass = available_proxies[request.class]
# Search for possible superclass matches.
if klass.nil?
request_parent = available_proxies.keys.find { |rc| request.is_a?(rc) }
klass = available_proxies[request_parent]
end
raise UnknownRequestType, request.class.to_s unless klass
By requiring 'oauth/request_proxy/typhoeus_request', Typhoeus::Request inherits OAuth::RequestProxy::Base and raising UnknownRequestType error can be avoided.
https://github.com/oauth-xx/oauth-ruby/blob/master/lib/oauth/request_proxy/typhoeus_request.rb
I am trying to learn how to make simple requests to the Coinbase API in Ruby. This is mostly for my own entertainment and education. The Ruby GEM is so out of date I thought I'd start working on my own system instead of relying on it. To that end I wanted to get the basics working before I tried to turn it into a gem.
Here is the beginnings of my module.
And no matter what, when I try to get /accounts I get a 401 response.
#status=401 #reason_phrase="Unauthorized" #response_body="{\"message\":\"Invalid API Key\"}
# Simple Coinbase Requests
require 'base64'
require 'openssl'
require 'json'
require 'faraday'
module Coinbase
module Request
class API
def initialize key = '', secret = '', passphrase = '', sandbox = false
#url = sandbox ? 'https://api-public.sandbox.pro.coinbase.com' : 'https://api-public.pro.coinbase.com'
#key = key
#secret = secret
#passphrase = passphrase
#content_type = 'application/json'
end
# Get Accounts
def accounts
self.get '/accounts'
end
# Do the work of a HTTP Get
def get path
timestamp = Time.now.to_i.to_s
headers = auth_headers path, '', 'GET', timestamp
connection = Faraday.new(url: 'https://api-public.sandbox.pro.coinbase.com', headers: headers, ssl: { :verify => false })
# puts connection.inspect
connection.get path
end
# Auth Headers
# CB-ACCESS-KEY The api key as a string.
# CB-ACCESS-SIGN The base64-encoded signature (see Signing a Message).
# CB-ACCESS-TIMESTAMP A timestamp for your request.
# CB-ACCESS-PASSPHRASE The passphrase you specified when
def auth_headers path = '', body = '', method = 'GET', timestamp = nil
{
'Content-Type': 'Application/JSON',
'CB-ACCESS-SIGN': self.signature(path, body, method, timestamp),
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': #key,
'CB-ACCESS-PASSPHRASE': #passphrase
}
end
# Generate a signature
def signature path = '', body = '', method = 'GET', timestamp = nil
body = body.to_json if body.is_a?(Hash)
secret = Base64.decode64 #secret
hash = OpenSSL::HMAC.digest 'sha256', secret, "#{timestamp}#{method}#{path}#{body}"
Base64.strict_encode64 hash
end
end
end
end
I'm calling it using the following (THE KEY/SECRET/PASSPHRASE ARE FAKE for this example)
coinbase = Coinbase::Request::API.new('123426bc3a583fb8393141fb7777fake',
'+FAKEbGoG1eT1WVFWNJxFtTE/y4kIYq2Lbf6FAKEw5j2756GXgaqg5iXTsqPJXKkStZ7nPoTT2RGKwiJfRFAKE==',
'FAKEPASSPHRASE',
true)
puts coinbase.accounts.inspect
The signature "what" (as defined as "#{timestamp}#{method}#{path}#{body}" in your docs) for this simple request would be something like 1624063589GET/accounts
The headers come out to {:"Content-Type"=>"Application/JSON", :"CB-ACCESS-SIGN"=>"rs29GSZuRspthioywb5IkaHQmPIwH5DRDW5LHoYUvw8=", :"CB-ACCESS-TIMESTAMP"=>"1624063726", :"CB-ACCESS-KEY"=>"123426bc3a583fb8393141fb22328113", :"CB-ACCESS-PASSPHRASE"=>"FAKEPASSPHRASE"}
Once connected the headers come out as seen below in the faraday response object.
This is the full response object output which is what I would expect to get with fake keys as used but I get the same thing when I use the keys I generated here: https://pro.coinbase.com/profile/ap
#<Faraday::Response:0x00000001621a8e58 #on_complete_callbacks=[], #env=#<Faraday::Env #method=:get #url=#<URI::HTTPS https://api-public.sandbox.pro.coinbase.com/accounts> #request=#<Faraday::RequestOptions (empty)> #request_headers={"Content-type"=>"Application/JSON", "Cb-access-sign"=>"vEfjUnFy+3qQqRa2lxvEC5O32xOa6t7NgGAxO8OYrpo=", "Cb-access-timestamp"=>"1624063280", "Cb-access-key"=>"123426bc3a583fb8393141fb22328113", "Cb-access-passphrase"=>"FAKEPASSPHRASE", "User-Agent"=>"Faraday v1.4.2"} #ssl=#<Faraday::SSLOptions (empty)> #response=#<Faraday::Response:0x00000001621a8e58 ...> #response_headers={"date"=>"Sat, 19 Jun 2021 00:41:21 GMT", "content-type"=>"application/json; charset=utf-8", "content-length"=>"29", "connection"=>"keep-alive", "access-control-allow-headers"=>"Content-Type, Accept, cb-session, cb-fp, cb-form-factor", "access-control-allow-methods"=>"GET,POST,DELETE,PUT", "access-control-allow-origin"=>"*", "access-control-expose-headers"=>"cb-before, cb-after, cb-gdpr", "access-control-max-age"=>"7200", "cache-control"=>"no-store", "etag"=>"W/\"1d-mmRSeO9uba2rhQtGfy4YjixIkt4\"", "strict-transport-security"=>"max-age=15552000; includeSubDomains", "x-content-type-options"=>"nosniff", "x-dns-prefetch-control"=>"off", "x-download-options"=>"noopen", "x-frame-options"=>"SAMEORIGIN", "x-xss-protection"=>"1; mode=block", "cf-cache-status"=>"MISS", "cf-request-id"=>"0ac3501f6300005ae1c99c3000000001", "expect-ct"=>"max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"", "vary"=>"Accept-Encoding", "server"=>"cloudflare", "cf-ray"=>"6618b6123ba75ae1-IAD"} #status=401 #reason_phrase="Unauthorized" #response_body="{\"message\":\"Invalid API Key\"}">>
I've been plugging away at this for days and I just can't seem to get it to not return Invalid API Key. I feel like I'm so very close.. but no cigar..
Thoughts?
NOTE: On a whim before submitting this I tried /currencies and it worked fine.
It took me some trial and error with this too, but I was hitting invalid signature errors rather than invalid API key errors. Maybe this will help anyway:
Working example:
Gemfile
source 'https://rubygems.org'
ruby '2.6.7'
gem 'httparty'
coinbase.rb
require 'base64'
require 'httparty'
API_KEY = ''
API_PASSPHRASE = ''
API_SECRET = ''
key = Base64.decode64(API_SECRET)
url = "https://api.exchange.coinbase.com"
requestPath = "/accounts"
method = "GET"
body = ""
timestamp = Time.now.to_i.to_s
message = timestamp + method + requestPath + body
hmac = OpenSSL::HMAC.digest('sha256', key, message)
signature = Base64.strict_encode64(hmac)
headers = {
'Content-Type': 'application/json',
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': API_KEY,
'CB-ACCESS-PASSPHRASE': API_PASSPHRASE
}
result = HTTParty.get(url+requestPath, headers: headers)
puts result.response.body
Run:
bundle install
bundle exec coinbase.rb
I'm new to Box Api and ruby. I am trying trying refreshing token, but I'm not sure about what is token_refresh_callback in the below code
client = Boxr::Client.new('zX3UjFwNerOy5PSWc2WI8aJgMHtAjs8T',
refresh_token: 'dvfzfCQoIcRi7r4Yeuar7mZnaghGWexXlX89sBaRy1hS9e5wFroVVOEM6bs0DwPQ',
client_id: 'kplh54vfeagt6jmi4kddg4xdswwvrw8y',
client_secret: 'sOsm9ZZ8L8svwrn9FsdulLQVwDizKueU',
&token_refresh_callback)
Also, once my access token is expired, does this method revoke the token?
Thanks for the help!
Using the Access and Refresh Tokens
The access_token is the actual string needed to make API requests.Each access_token is valid for 1 hour.In order to get a new, valid token, you can use the accompanying refresh_token.Each refresh_token is valid for one use in 60 days.Every time you get a new access_token by using a refresh_token,
we reset your timer for the 60 day period and hand you a new refresh_token.
This means that as long as your users use your application once every 60 days, their login is valid forever.
In box_api_controller.rb file
def make_request
#Check access token expire or not.
check_access_token_expire = check_access_token_expire_dt
if check_access_token_expire.split("-")[0] == "access_token"
#Create client by passing Token
#box_client = Boxr::Client.new(check_access_token_expire.split("-")[1])
cookies[:token] = check_access_token_expire.split("-")[1]
else
if check_access_token_expire.split("-")[0] == "refresh_token"
#Call method
create_post_req_url("refresh_token","refresh_token",check_access_token_expire.split("-")[1])
else
# kick off authorization flow
parameters = "response_type=code&client_id=<your client id>&redirect_uri=<your application url>/handle_user_decision/&state=security_token"
url = "https://account.box.com/api/oauth2/authorize?#{parameters}"
redirect_to url
end
end
end
After authorized the client id, get code in response
def handle_user_decision
# kick off authorization flow
#Get authorization code
code_url = Rack::Utils.parse_query URI(request.original_url).query
code = code_url["code"]
#Call method
create_post_req_url("authorization_code","code", code)
end
Create Post Url
def create_post_req_url(grant_type,header, code)
#Set oauth2 url
uri = URI.parse("https://api.box.com//oauth2//token")
#Passing parameter
data = "grant_type=#{grant_type}&#{header}=#{code}&client_id=<your client id>&client_secret=<your client secret key>"
#Set header
headers = {"Content-Type" => "application/x-www-form-urlencoded"}
#Get http request
http = Net::HTTP.new(uri.host,uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#Do post the URL
response = http.post(uri.path,data.to_s,headers)
#Check response
if response.code != "200"
flash[:alert] =":#{response.code} #{JSON.parse(response.body)}"
else
#flash[:alert] ="#{response.body.to_json}"
parsed = JSON.parse(response.body) # returns a hash
token = parsed["access_token"]
cookies[:token] = nil
cookies[:token] = token
if grant_type == "authorization_code"
#Insert BOX access token details
user = "<your drive user name>"
insert_access_token(user, token, parsed["refresh_token"], Time.now)
else
if grant_type == "refresh_token"
#Update BOX access token
updt_access_token(user, token, code, parsed["refresh_token"], Time.now)
end
end
redirect_to box_api_index_path
end
end
Check access token expire or not
def check_access_token_expire_dt
#access_token_time = BoxApiAccessToken.getaccesstokentime
if !#access_token_time.blank?
#access_token_time.each do |token_details |
if token_details.access_token_dt != nil
if token_details.new_access_token_dt.to_datetime.new_offset(Rational(9, 24)).strftime('%Y/%m/%d %H:%M') < Time.now.to_datetime.new_offset(Rational(9, 24)).strftime('%Y/%m/%d %H:%M')
check_access_token_expire_dt = "refresh_token-#{token_details.refresh_access_token}"
return check_access_token_expire_dt
else
check_access_token_expire_dt = "access_token-#{token_details.access_token}"
return check_access_token_expire_dt
end
else
check_access_token_expire_dt = "new_token-req_new_token"
return check_access_token_expire_dt
end
end
else
check_access_token_expire_dt = "new_token-req_new_token"
return check_access_token_expire_dt
end
end
In Model
def insert_access_token(user,access_token,refresh_access_token,access_token_dt)
#box_access_token = BoxApiAccessToken.new(
:user => user,
:access_token => access_token,
:refresh_access_token => refresh_access_token,
:access_token_dt => access_token_dt)
#Save User Device Data
#box_access_token.save
end
#Update access_token,refresh_access_token,access_token_dt details in DB
def updt_access_token(user,access_token, refresh_access_token,new_refresh_access_token,access_token_dt)
##box_access_token_updt = BoxApiAccessToken.find_refresh_access_token(refresh_access_token)
#box_access_token_updt = BoxApiAccessToken.find_by_refresh_access_token(refresh_access_token)
attributes = {:access_token => access_token,:access_token_dt => access_token_dt, :refresh_access_token => new_refresh_access_token, :updated_at => access_token_dt}
#Update the object
#box_access_token_updt.update_attributes(attributes)
end
In index.html.erb
<%= form_tag(:controller => "box_api", :action => 'make_request') do |f| %>
<div class="form-group"><%= submit_tag("Box Login", class: "btn btn-primary") %></div><% end %>
I just want to want to request an access token and then after it expires refresh it using refresh token and cycle. How do you think i can do it?
I using oauth-ruby gem for a while, and I already implemented 2 types of auth:
default one
and custom, which uses OTP sent via sms
Both of them works perfectly now
But now i'm trying to implement new(3) 2-legged oauth. And I ran in to issues which I actually can't understand.
All my signed requests using access token from (3) are failing because of invalid token. For (1-2) it works without any issues.
Signing requests is implemented via RestClient.before_execution_proc:
RestClient.add_before_execution_proc do |req, params|
access_token.sign!(req)
end
I suppose the problem comes from access_token = OAuth::AccessToken as there actual difference between other 2.
Any suggestions or advices will be very helpful
1.
def default_oauth(login, pass, device: Device.new)
#cookies = login_req(login, pass).cookies
headers = common_headers.merge("Cookie" => #cookies)
#Get request token
request_token = consumer.get_request_token
# Authorize request key
authorize = RestClient.post(base_url + '/oauth/authorize',
{ requestToken: request_token.token, authorize: 'Authorize'},
headers) {|response, request, result| response }
auth_url_resp = RestClient.get(authorize.headers[:location], headers: headers) {|response, request, result| response }
# Get Access key
access_token = request_token.get_access_token
end
2.
def custom_oauth(phone, pin, otp: nil, device: Device.new)
otp = phone.to_s[-5..-1] if otp.nil?
resp = RestClient.post("#{base_url}/rest/smartphone/otp/sms-sender/#{phone}", '', common_headers) {|response, request, result| response }
request_token = consumer.get_request_token
payload = {
device: device.to_h,
otp: otp,
password: pin.to_s,
requestToken: request_token.token
}
headers = json_headers.merge('Cookie' => otp)
authorize = RestClient.post(base_url + '/oauth/otp-authorization',
payload.to_json, headers) {|response, request, result| response }
#access_token = request_token.get_access_token
end
3.
def new_oauth(login, pass, entry, device: Device.new)
tkn = consumer.get_request_token.token
payload = {
username: login,
password: pass.to_s,
requestToken: tkn,
entryPoint: entry,
device: device.to_h
}
headers =json_headers(device.id)
resp = RestClient.post("#{base_url}/oauth/login-authorization", payload.to_json, headers) {|response, request, result| response}
hsh ={oauth_token: resp.headers[:accesstoken], oauth_token_secret: resp.headers[:tokensecret] }
access_token = OAuth::AccessToken.from_hash(consumer, hsh)
end
Consumer:
def consumer
#consumer ||= build_consumer
end
def build_consumer
key = 'key_string'
secret ='secret_string'
OAuth::Consumer.new(key, secret, :site => base_url)
end
Issue was related to server (Spring) encoding stuff. oauth-ruby gem is escaping token secret (Combined secret or Encryption key) which is used for signature creation. Spring by default doing the same on server side.
Unescaping access_token.secret fixed this issue:
access_token.secret = OAuth::Helper.unescape(access_token.secret)
I've got a Sinatra/Warden Remote API, and a client in RubyMotion.
How can I post the Authentication Token and User Object with AFMotion for initial registration (from client)?
This more or less what I have so far, not much I know.
Basically I need to pass through a token to the remote api and a user object.
def register_user(user)
#client = AFMotion::Client.build("http://localhost:9393/register") do
header "Accept", "application/json"
request_serializer: :json
response_serializer :json
end
end
Help?
You can change the line you initiate #client object to something like this
#client = AFMotion::Client.build("http://localhost:9393/") do
header "Accept", "application/json"
response_serializer :json
end
and when you want to do a POST request, you can do
#client.post('register', {
token: 'TOKEN HERE',
user: 'USER OBJECT HERE'
}) do |result|
puts result
end
You can find out more here.