OAuth and HTTParty - ruby

Is it possible to use OAuth with HTTParty? I'm trying to do this API call, but, contradictory to the documentation, it needs authentication.
Before you say "Use a Twitter-specific Gem", hear me out--I've tried. I've tried twitter, grackle, and countless others, but none support this specific API call. So, I've turned to HTTParty.
So, how could I use OAuth with HTTParty?

I've been using the vanilla OAuth gem to implement a few simple Twitter API calls. I didn't need a heavyweight gem to do everything, and I was already using OAuth, so a 'roll-your-own' approach seemed reasonable. I know that I haven't mentioned HTTParty, so please don't ding me for that. This may be useful to others for the essence of easy Twitter OAuth if you're already using the OAuth gem.
In case it is helpful, here is the pertinent code (sorry about mixing some constants and other variables / methods at the start - it was the easiest and most accurate way to extract this from my real code):
#Set up the constants, etc required for Twitter OAuth
OAUTH_SITE = "https://api.twitter.com"
TOKEN_REQUEST_METHOD = :post
AUTHORIZATION_SCHEME = :header
def app_request_token_path
"/oauth/request_token"
end
def app_authorize_path
"/oauth/authorize"
end
def app_access_token_path
"/oauth/access_token"
end
def consumer_key
"your twitter API key"
end
def consumer_secret
"your twitter API secret"
end
# Define the OAuth consumer
def consumer meth=:post
#consumer ||= OAuth::Consumer.new(consumer_key,consumer_secret, {
:site => "#{OAUTH_SITE}",
:request_token_path=>app_request_token_path,
:authorize_path=>app_authorize_path,
:access_token_path=>app_access_token_path,
:http_method=>:post,
:scheme=> :header,
:body_hash => ''
})
end
# Essential parts of a generic OAuth request method
def make_request url, method=:get, headers={}, content=''
if method==:get
res = #access_token.get(url, headers)
elsif method==:post
res = #access_token.post(url, content, headers)
end
if res.code.to_s=='200'
jres = ActiveSupport::JSON.decode(res.body)
if jres.nil?
#last_status_text = #prev_error = "Unexpected error making an OAuth API call - response body is #{res.body}"
end
return jres
else
#last_status_text = #prev_error = res if res.code.to_s!='200'
return nil
end
end
# Demonstrate the daily trends API call
# Note the use of memcache to ensure we don't break the rate-limiter
def daily_trends
url = "http://api.twitter.com/1/trends/daily.json"
#last_status_code = -1
#last_status_success = false
res = Rails.cache.fetch(url, :expires_in=> 5.minutes) do
res = make_request(url, :get)
unless res
#last_status_code = #prev_error.code.to_i
end
res
end
if res
#last_status_code = 200
#last_status_success = true
#last_status_text = ""
end
return res
end
I hope this, largely in context of broader use of the OAuth gem, might be useful to others.

I don't think that HTTParty supports OAuth (though I am no expert on HTTParty, it's way too high-level and slow for my taste).
I would just call the Twitter request directly using OAuth gem. Twitter API documentation even has an example of usage: https://dev.twitter.com/docs/auth/oauth/single-user-with-examples#ruby

I used a mix of the OAuth2 gem to get the authentication token and HTTParty to make the query
client = OAuth2::Client.new(apiKey, apiSecret,
:site => "https://SiteForAuthentication.com")
oauthResponse = client.password.get_token(username, password)
token = oauthResponse.token
queryAnswer = HTTParty.get('https://api.website.com/query/location',
:query => {"token" => token})
Not perfect by a long way but it seems to have done the trick so far

Related

SSO Auth flow w/ DocuSign_eSign gem: "The specified Integrator Key was not found or is disabled."

I am using the DocuSign_eSign ruby gem version 1.0.2 because version 2.0.0 does not handle JWT auth. The eg-01-ruby-jwt example provided by DocuSign uses v1.0.2 as well, so I just pinned this version in my Gemfile.
I am getting the following error when I attempt to make a call using the DocuSign_eSign::EnvelopesApi.new#create_envelope method:
docusign_esign-1.0.2/lib/docusign_esign/api_client.rb:66:in `call_api': Bad Request (DocuSign_eSign::ApiError)
For good measure, I walked through the code line by line to get a better idea of what was happening, and I ended up with this error:
irb(main):101:0> response.code
=> 401
irb(main):103:0> response.body
=> "{\r\n \"errorCode\": \"PARTNER_AUTHENTICATION_FAILED\",\r\n \"message\": \"The specified Integrator Key was not found or is disabled. An Integrator key was not specified.\"\r\n}"
irb(main):104:0> response.status_message
=> "Unauthorized"
Here is the ruby code (minus logging/error handling) from my DocuSignWebClient where I trigger the call (send_envelope). This is where the auth happens:
TOKEN_REPLACEMENT_IN_SECONDS = 10.minutes.seconds
TOKEN_EXPIRATION_IN_SECONDS = 1.hour.seconds
def initialize(options = {})
#docusign_config = Padrino.config.docusign
#api_client = DocuSignWebClient.setup_api_client
#s3_client = options[:s3_client] || Aws::S3::Client.new(region: Padrino.config.aws.region)
#token_expire_in = options[:token_expire_in] || 0
#account_id = options[:account_id]
end
def self.setup_api_client
configuration = DocuSign_eSign::Configuration.new
DocuSign_eSign::ApiClient.new(configuration)
end
def send_envelope(details)
authorize
envelope = DocuSign::EnvelopeBuilder.new(details, #s3_client).build_envelope
sender = DocuSign::EnvelopeSender.new(#api_client, #account_id)
response = sender.send_envelope(envelope)
update_document_status(details, response)
end
def authorize
check_token
#account_id ||= update_account_id
end
def check_token
if no_token? || token_near_expiration?
update_token
end
end
def no_token?
!#api_client.default_headers['Authorization']
end
def token_near_expiration?
now = Time.now.to_f
(now + TOKEN_REPLACEMENT_IN_SECONDS) > #token_expire_in
end
def update_token
configure_jwt_authorization_flow
#token_expire_in = Time.now.to_f + TOKEN_EXPIRATION_IN_SECONDS
end
def configure_jwt_authorization_flow
#api_client.configure_jwt_authorization_flow(#docusign_config[:private_key_file],
#docusign_config[:auth_server],
#docusign_config[:integrator_key],
#docusign_config[:user_id],
TOKEN_EXPIRATION_IN_SECONDS)
end
def update_account_id
account = fetch_account_info
#api_client.config.host = account[:base_uri]
account[:account_id]
end
def fetch_account_info
response = DocuSignResponse.new(#api_client.call_api('GET', "https://#{#docusign_config[:auth_server]}/oauth/userinfo", return_type: 'Object'))
if response.ok?
response.data[:accounts].detect { |acct| acct[:is_default] }
end
end
I am getting a token back from DocuSign after I go through the authorization flow, but it says that the token is not valid once I try to use it.
irb(main):033:0> account_id = client.authorize
=> "386...a24"
irb(main):036:0> client.api_client
=> #<DocuSign_eSign::ApiClient:0x00007ff6d243c8c8 #config=#<DocuSign_eSign::Configuration:0x00007ff6d243d2f0 #scheme="https", #host="demo.docusign.net", #base_path="/restapi", #api_key={}, #api_key_prefix={}, #timeout=0, #verify_ssl=true, #verify_ssl_host=true, #params_encoding=nil, #cert_file=nil, #key_file=nil, #debugging=false, #inject_format=false, #force_ending_format=false, #logger=#<Logger:0x00007ff6d243d110 #level=0, #progname=nil, #default_formatter=#<Logger::Formatter:0x00007ff6d243d098 #datetime_format=nil>, #formatter=nil, #logdev=#<Logger::LogDevice:0x00007ff6d243cfa8 #shift_period_suffix=nil, #shift_size=nil, #shift_age=nil, #filename=nil, #dev=#<IO:<STDOUT>>, #mon_mutex=#<Thread::Mutex:0x00007ff6d243c8f0>, #mon_mutex_owner_object_id=70349033170900, #mon_owner=nil, #mon_count=0>>>, #user_agent="Swagger-Codegen/1.0.2/ruby", #default_headers={"Content-Type"=>"application/json", "User-Agent"=>"Swagger-Codegen/1.0.2/ruby", "Authorization"=>"Bearer eyJ0eXAiOiJNVCIsImFsZyI6IlJTMjU2Iiwia2lkIjoiNjgxODVmZjEtNGU1MS00Y2U5LWFmMWMtNjg5ODEyMjAzMzE3In0.AQkAAAABAAUABwAACzCudSXXSAgAAHP0D34l10gCABIJ3tlGgoJMvWi9_zzeFocVAAEAAAAYAAEAAAAFAAAADQAkAAAAZTVjOTIwMTItMWI0ZC00ZTgzLTgzNjYtNjgzNDQ0NjQyNjc0IwAkAAAAZTVjOTIwMTItMWI0ZC00ZTgzLTgzNjYtNjgzNDQ0NjQyNjc0EgABAAAABgAAAGp3dF9icg.yt_0QtjwAcL1dosfVuaNoKoM3Yzq9DK4MUf6lx3Sp5EYy0OdeSaKt6TgsAujUNQQoQH4e_IZFGtVXxBjFXzP2hh9EB2nsdwKAzi5EZJcOIp1wEfzKjEllUnOXOkEoUwcmHHCSN1j4LfNF8olRTDJnaSDB9A5TbumLURC_-FkttxHitFVpt39Fvl85VtSlIsQxU544SRjeJGJMl_BDwRmu0JrUSawc5LSUF9ET9SVTBGEjS_vZMz92hdiFM2x4qZqupeSXLrQ92bhzjEXHOH7kmKbE-iKDTH_TOln0rhhqLXq25yOTBJ_yUWqhvYaxpct9GRuPo6IIZCDDv0Of7k-xQ"}>
Does anything in that API instance look wrong? I can't figure out why my token is not working.
UPDATE: I re-attempted the same flow with a token using the OAuth Token Generator and that token also fails with the same error. Additionaly, I am able to send a token using the eg-01-ruby-jwt example code with the SAME EXACT inputs when calling DocuSign_eSign::ApiClient#configure_jwt_authorization_flow. The Envelopes API takes the API client and the account ID, which are identical upon inspection between the example code and my code (besides the auth token).
Try to use the token generator (https://developers.docusign.com/oauth-token-generator) and see if that token works for you. That would isolate the issue to obtaining the token.
Make sure in the sandbox admin, you configured your IK correctly, and that you use the exact same one.
You need an RSA key generated and make sure to use it for JWT.
Make sure you point to account-d.docusign.com and not account.docusign.com (so you use our developer sandbox and not production)

How to link OmniAuth identification (working) and Google Api Calls

After a day trying, testing, googling ... I ask for some help
I try to use Omniauth and Google Calendar. OmniAuth is working like a charm but I just can't link it with Google API
I think I read almost everything, I still get this error message :
dailyLimitExceededUnreg: Daily Limit for Unauthenticated Use Exceeded.
Continued use requires sign up.
It means that my calls are not properly 'connected' to my auth, which seems to be valid. My tokens are in databases but I this point I would like to login / identify / call and have something else that an error message.
client_id = Google::Auth::ClientId.from_file('.....googleusercontent.com.json')
scopes = ['userinfo.email,calendar']
token_store = Google::Auth::MyTokenStore.new()
authorizer = Google::Auth::WebUserAuthorizer.new(
client_id,
scopes,
token_store,
'http://localhost:3000'
)
# credentials = Google::Auth::UserAuthorizer.new( . # Anotheir test
# client_id,
# scopes,
# token_store,
# 'http://localhost:3000'
# )
#
# authorizer = credentials.get_credentials_from_code(
# GoogleUser.find(session[:user_id]) # I tested token ... notking worked
# )
calendar = Google::Apis::CalendarV3::CalendarService.new
calendar.authorization = authorizer
calendar_id = 'primary'
#result = calendar.list_events(calendar_id,
max_results: 10,
single_events: true,
order_by: 'startTime',
time_min: Time.now.iso8601)
and my token storage , I don't understand why but never called
class MyTokenStore
class << self
attr_accessor :default
end
def load(_id)
puts "********** load ************"
return GoogleUser.find(session[:user_id]).access_token
end
def store(_id, _token)
puts "********** store ************"
#user.access_token = _token
end
def delete(_id)
puts "********** delete ************"
#user.access_token = nil
end
end
end
end
For future readers : I took a different technic, after reading an excellent article here : http://gmile.me/simple-google-auth/
I followed it and use signet, it is working like a charm

Ruby Gmail API with OAUTH2 responds with Invalid credentials (Failure) when logging in

I am trying to connect to the Gmail api using the Gmail for Ruby gem. I'm following this google oauth2 guide for installed applications.
I have set my app up in the Google Developer's Console, I am able to send a request with my client_id and client_secret to obtain an authorization code. I am then able to send a request with my authorization code to obtain an access token and a refresh token. I am able to successfully send a request to refresh my access token, and it returns a new access token.
The problem arises when I try to connect to Gmail. First I set an instance variable #gmail = Gmail.connect(:xoauth2, #email, #client.access_token). Then, I attempt to login with #gmail.login(true). However, when I try that, I get the following error:
Couldn't login to given Gmail account: caiden.robinson35#gmail.com (Invalid credentials (Failure)) (Gmail::Client::AuthorizationError)
I am at a loss here, everything suggests I'm executing the oauth2 flow correctly, except the fact that when it comes time to login, I get invalid credentials. When generating my authorization code, I specifically click my email and allow my app to have access. The API is also enabled in my developers console. Here is the full code:
class GmailClient
def initialize
load_client_info
#email = "caiden.robinson35#gmail.com"
load_and_set_oauth2_tokens
sign_in_gmail
binding.pry
end
private
def sign_in_gmail
binding.pry
#gmail = Gmail.connect(:xoauth2, #email, #client.access_token)
######################
# RIGHT HERE IS WHERE IT FAIL
######################
#gmail.login true
binding.pry
end
def load_client_info
gmail_credentials = YAML.load_file('config/gmail.yml')
#client_id = gmail_credentials["client_id"]
#client_secret = gmail_credentials["client_secret"]
#redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
end
def load_and_set_oauth2_tokens use_cached_tokens = true
if use_cached_tokens && File.exist?("config/tokens.yml")
token_hash = YAML.load_file('config/tokens.yml')
#authorization_code = { code: token_hash["authorization_code"],
is_cached: true }
#client = signet_client(token_hash)
#token_hash = #client.refresh!
else
if !(instance_variable_defined?("#authorization_code") && #authorization_code[:is_cached] == false)
retrieve_and_set_authorization_code
end
#token_hash = set_client_and_retrieve_oauth2_tokens
end
write_tokens_to_file
end
def retrieve_and_set_authorization_code
puts "Go to the following url to enable the gmail cli app:"
puts "https://accounts.google.com/o/oauth2/auth?scope=email&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=#{#client_id}"
print "Paste your authorization code here: "
#authorization_code = { code: gets.chomp,
is_cached: false }
end
def set_client_and_retrieve_oauth2_tokens
#client = signet_client
#client.fetch_access_token!
end
def signet_client token_hash = nil
client = Signet::OAuth2::Client.new(
client_id: #client_id,
client_secret: #client_secret,
redirect_uri: #redirect_uri,
scope: 'email',
token_credential_uri: 'https://www.googleapis.com/oauth2/v4/token'
)
if token_hash.present?
client.refresh_token = token_hash["refresh_token"]
else
client.authorization_uri = 'https://accounts.google.com/o/oauth2/auth'
client.code = #authorization_code[:code]
end
client
end
def write_tokens_to_file
if File.exist?("config/tokens.yml")
data = YAML.load_file "config/tokens.yml"
#token_hash.each { |k, v| data[k] = v }
File.open('config/tokens.yml', 'w') do |file|
YAML.dump(data, file)
end
else
File.open('config/tokens.yml', 'w') do |file|
#token_hash.each { |k, v| file.write("#{k}: #{v}\n") }
file.write("authorization_code: #{#authorization_code[:code]}\n")
end
end
end
end
If my question is lacking any info, please just ask, I am eager to solve this.
Scopes matter. Here are right ones:
scope: ['https://mail.google.com/', 'https://www.googleapis.com/auth/userinfo.email' #,'https://www.googleapis.com/auth/gmail.send' - if you'd like to send emails as well]

How Do I search Twitter for a word with Ruby?

I have written code in Ruby that will display the timeline for a specific user. I would like to write code to be able to just search twitter to just find every user that has mentioned a word. My code is currently:
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(
"q" => "Obama"
)
address = URI("#{baseurl}#{path}?#{query}")
request = Net::HTTP::Get.new address.request_uri
# Print data about a list of Tweets
def print_timeline(tweets)
tweets.each do |tweet|
require 'date'
d = DateTime.parse(tweet['created_at'])
puts " #{tweet['text'].delete ","} , #{d.strftime('%d.%m.%y')} , #{tweet['user']['name']}, #{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
puts "Text,Date,Name,id"
if response.code == '200' then
tweets = JSON.parse(response.body)
print_timeline(tweets)
end
nil
How would I possibly change this code to search all of twitter for a specific word?
The easiest approach would be to use 'Twitter' gem. Refer to this Link for more information and the result type of the search results. Once you have all the correct authorization attribute in place (oAuth-Token,oAuth-secret, etc) you should be able to search as
Twitter.search('Obama')
or
Twitter.search('Obama', options = {})
Let us know, if that worked for you or not.
p.s. - Please mark the post as answered if it helped you. Else put a comment back with what is missing.
The Twitter API suggests the URI your should be using for global search is https://api.twitter.com/1.1/search/tweets.json and this means:
Your base_url component would be https://api.twitter.com
Your path component would be /1.1/search/tweets.json
Your query component would be the text you are searching for.
The query part takes a lot of values depending upon the API spec. Refer to the specification and you can change it as per your requirement.
Tip: Try to use irb (I'd recommend pry) REPL which makes it a lot easier to explore APIs. Also, checkout the Faraday gem which can be easier to use than the default HTTP library in Ruby IMO.

HTTP Basic Authentication with Anemone Web Spider

I need collect all "title" from all pages from site.
Site have HTTP Basic Auth configuration.
Without auth I do next:
require 'anemone'
Anemone.crawl("http://example.com/") do |anemone|
anemone.on_every_page do |page|
puts page.doc.at('title').inner_html rescue nil
end
end
But I have some problem with HTTP Basic Auth...
How I can collected titles from site with HTTP Basic Auth?
If I try use "Anemone.crawl("http://username:password#example.com/")" then I have only first page title, but other links have http://example.com/ style and I received 401 error.
HTTP Basic Auth works via HTTP headers. Client, willing to access restricted resource, must provide authentication header, like this one:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
It contains name and password, Base64-encoded. More info is in Wikipedia article: Basic Access Authentication.
I googled a little bit and didn't find a way to make Anemone accept custom request headers. Maybe you'll have more luck.
But I found another crawler that claims it can do it: Messie. Maybe you should give it a try
Update
Here's the place where Anemone sets its request headers: Anemone::HTTP. Indeed, there's no customization there. You can monkeypatch it. Something like this should work (put this somewhere in your app):
module Anemone
class HTTP
def get_response(url, referer = nil)
full_path = url.query.nil? ? url.path : "#{url.path}?#{url.query}"
opts = {}
opts['User-Agent'] = user_agent if user_agent
opts['Referer'] = referer.to_s if referer
opts['Cookie'] = #cookie_store.to_s unless #cookie_store.empty? || (!accept_cookies? && #opts[:cookies].nil?)
retries = 0
begin
start = Time.now()
# format request
req = Net::HTTP::Get.new(full_path, opts)
response = connection(url).request(req)
finish = Time.now()
# HTTP Basic authentication
req.basic_auth 'your username', 'your password' # <<== tweak here
response_time = ((finish - start) * 1000).round
#cookie_store.merge!(response['Set-Cookie']) if accept_cookies?
return response, response_time
rescue Timeout::Error, Net::HTTPBadResponse, EOFError => e
puts e.inspect if verbose?
refresh_connection(url)
retries += 1
retry unless retries > 3
end
end
end
end
Obviously, you should provide your own values for the username and password params to the basic_auth method call. It's quick and dirty and hardcode, yes. But sometimes you don't have time to do things in a proper manner. :)

Resources