how to properly authenticate user with devise_token_auth rails gem? - ruby-on-rails

I am trying to write a simple test for a URL which should be visible only to logged in user (I use before_action :authenticate_user! to achieve this).
Even though I (think) that I properly assign all the headers I always get JSON response {"errors":["Authorized users only."]}
My test looks like this (Login (to get header info) then visit protected URL with all the headers needed)
post "/api/v1/auth/sign_in", {:email => user.email, password: 'password'}
header_hash = {
'access-token' => response.headers['access-token'],
'uid' => response.headers['uid'],
'client' => response.headers['client'],
'expiry' => response.headers['expiry']
}
get "/api/v1/subscription_status", nil , header_hash
What am I doing wrong?

I didn't know that authentication token is changing on each request by default.
conf/device_token_auth.rb
config.change_headers_on_each_request = false
I set this setting to false and afterwards I got responses which I expected.

Related

Rails stub method on initialized ActiveRecord object

I want to stub set_user_tokens which is executed on the initialized (not saved) ActiveRecord object. This method assigns a token to the login object.
class AwareLogin < Authenticatable
def authenticate!
login = Login.find_or_initialize_for_authentication(params[:login][:email], 'aware')
class << login
include AwareAuth
end
if login.valid_password?(password) || (set_token = login.id.nil? && aware_response.success?)
login.set_user_tokens(aware_response) if set_token
success!(login)
else
aware_response.success? ? fail!(:aware_auth) : raise(ActiveRecord::Rollback)
end
end
end
end
So I want to stub setu_user_tokens method:
login.set_user_tokens(aware_response) if set_token
to receive login ActiveRecord object with attributes of oauth_tokens like below:
login.oauth_token
=> {"access_token" => return_token,"refresh_token" => nil,"token_expiration" => 1200 }
I've tried:
allow_any_instance_of(Login).to receive(:set_user_tokens).with(status: 200, body: { access_token: return_token }.to_json, success?: true).and_return(
oauth_tokens: {
"access_token" => return_token,
"refresh_token" => nil,
"token_expiration" => 1200 },
)
But I'm getting an error:
Login does not implement #set_user_tokens
I would be willing to bet your issue is set_user_tokens is part of AwareAuth.
Since you are only including this module in the eigenclass of the instance (login) as part of the AwareLogin#authenticate! method, the Login class does not implement that method at any point in time.
Is there a reason you are doing it this way rather than just including AwareAuth in the Login class in the first place?
Either way, while your question appears to lack context for the test itself, if I understand correctly, we should be able to resolve these issues as follows:
it 'sets user tokens' do
login = Login
.find_or_initialize_for_authentication('some_email#example.com', 'aware')
.tap {|l| l.singleton_class.send(:include, AwareAuth) }
allow(Login).to receive(:find_or_initialize_for_authentication).and_return(login)
allow(login).to receive(:set_user_tokens).and_return(
oauth_tokens: {
"access_token" => return_token,
"refresh_token" => nil,
"token_expiration" => 1200 }
)
#perform your action and expectations here
end
By using partial doubles you can stub the specific methods you need to without impacting any other functionality of the object itself.

bulk email using send grid in rails

Context:
I need to send bulk-email using send grid in a rails app.
I will be sending emails to maybe around 300 subscribers.
I have read that it can be accomplished using
headers["X-SMTPAPI"] = { :to => array_of_recipients }.to_json
I have tried following that.
The following is my ActionMailer:
class NewJobMailer < ActionMailer::Base
default from: "from#example.com"
def new_job_post(subscribers)
#greeting = "Hi"
headers['X-SMTPAPI'] = { :to => subscribers.to_a }.to_json
mail(
:to => "this.will#be.ignored.com",
:subject => "New Job Posted!"
)
end
end
I call this mailer method from a controller
..
#subscribers = Subscriber.where(activated: true)
NewJobMailer.new_job_post(#subscribers).deliver
..
The config for send-grid is specified in the config/production.rb file and is correct, since I am able to send out account activation emails.
Problem:
The app works fine without crashing anywhere, but the emails are not being sent out.
I am guessing the headers config is not being passed along ?
How can I correct this ?
UPDATE:
I checked for email activity in the send grid dashboard.
Here is a snapshot of one of the dropped emails:
You are grabbing an array of ActiveRecord objects with
#subscribers = Subscriber.where(activated: true)
and passing that into the smtpapi header. You need to pull out the email addresses of those ActiveRecord objects.
Depending on what you called the email field, this can be done with
headers['X-SMTPAPI'] = { :to => subscribers.map(&:email) }.to_json

How do I insert X-PAYPAL headers with Rails Invoice SDK?

I'm using the paypal SDK for invoicing located here:
https://github.com/paypal/invoice-sdk-ruby
This works great.
I integrated the paypal permissions SDK for rails:
https://github.com/paypal/permissions-sdk-ruby
The authorization workflow is working great.
So now I need to put them together. The documentation for the permissions sdk leaves off after you get your token. It doesn't explain how to use it with the other paypal SDKs (at least not so I could understand :D ) The invoice sdk tells you to see the Auth sdk.
Paypal tells me:
# Third-party Auth headers
-H "X-PAYPAL-SECURITY-SUBJECT:<receiverEdress>" # Merchant's PayPal e-mail
-H "X-PAYPAL-AUTHENTICATION:<OAuthSig>" # Generated OAuth Signature
Don't know how to insert that. The request is generated here in my model:
#create_and_send_invoice = api.build_create_and_send_invoice(paypalized || default_api_value)
The data itself is assembled in the invoice model like so:
paypalized = {
:access_token => self.user.paypal_token,
:invoice => {
:merchantEmail => self.user.paypal_email || self.user.email,
:payerEmail => self.client.email,
:itemList => #itemlist,
:currencyCode => "USD",
:paymentTerms => "DueOnReceipt",
:invoiceDate => self.updated_at,
:number => self.name,
:note => self.description,
:merchantInfo => #businessinfo
# project / Invoice title?
} # end invoice
} # end paypalized
return paypalized
This implementation is not working and the access_token field is being rejected. I looked through the gems associated with the sdks but can't see where the headers themselves are built or how to interact with that.
UPDATE: Found this which gives me a clue...
INVOICE_HTTP_HEADER = { "X-PAYPAL-REQUEST-SOURCE" => "invoice-ruby-sdk-#{VERSION}" }
This seems to be used here during calls in the paypal-sdk-invoice gem:
# Service Call: CreateAndSendInvoice
# #param CreateAndSendInvoiceRequest
# #return CreateAndSendInvoiceResponse
def CreateAndSendInvoice(options = {} , http_header = {})
request_object = BuildCreateAndSendInvoice(options)
request_hash = request_object.to_hash
...
I notice that there's two arguments: options and http_header. It's possible I can modify the http_header argument and pass it this way in my controller:
#create_and_send_invoice_response = api.create_and_send_invoice(#create_and_send_invoice, #cutsom_header)
or maybe
#create_and_send_invoice = api.build_create_and_send_invoice(data, custom_header)
I'll keep this updated since I googled around a lot and couldn't find any clear answers on how to do this...
You have to pass the token and token_secret while creating API object for third-party authentication.
#api = PayPal::SDK::Invoice::API.new({
:token => "replace with token",
:token_secret => "replace with token-secret" })

Rails, OmniAuth, google_oauth2, google-api-client, Moments.insert... 401 unauthorized... why?

I haven't been able to find an answer to this online -- aside from using the Google+ Sign In button, which I don't want to use at this point because I don't want to get into Javascript if I don't have to.
I have a Ruby on Rails application (ruby v1.9.3, rails v3.2.13) in which I've hooked up OmniAuth and I'm using the google_oauth2 gem to integrate with Google+.
My simple goal is to allow a user to authenticate with Google+, grant access to my Google API project, and then be able to post a moment to the Google+ user's vault using the google-api-client gem.
I have already setup my Google API Project, created the OAuth 2.0 for Web Applications, and enabled Google+ API service.
I have OmniAuth setup with the following provider and I've added the request_visible_actions option to allow me to post (I think this is correct but haven't seen this used from any code examples I've looked at online...):
provider :google_oauth2, CLIENT_ID, CLIENT_SECRET, {
access_type: 'offline',
scope: 'userinfo.email,userinfo.profile,plus.me,https://www.googleapis.com/auth/plus.login',
request_visible_actions: 'http://schemas.google.com/AddActivity',
redirect_uri: 'http://localhost/auth/google_oauth2/callback'
}
When I redirect my user to /auth/google_oauth2, it sends the user to Google+ to authorize my app and when the user approves, it returns to my callback where I can access the request.env["omniauth.auth"] and it has all the information I would expect, including tokens, email address, etc. I'm storing the access_token from auth["credentials"]["token"].
So far so good, right?
When I try to post the moment using the following code, I encounter an exception indicating a 401 unauthorized error.
client = Google::APIClient.new
client.authorization.access_token = self.access_token
plus = client.discovered_api('plus', 'v1')
moment = {
:type => 'http://schemas.google.com/AddActivity',
:target => { :id => Time.now.to_i.to_s,
:description => message,
:name => message
}
}
# post a moment/activity to the vault/profile
req_opts = { :api_method => plus.moments.insert,
:parameters => { :collection => 'vault', :userId => 'me', },
:body_object => moment
}
response = client.execute!(req_opts).body
I have also tried replacing
client.authorization.access_token = self.access_token
with
credentials = Hash.new
credentials[:access_token] = self.access_token
credentials[:refresh_token] = self.refresh_token
credentials[:expires_at] = self.expires_at
client.authorization.update_token!(credentials)
But no luck.
I think the issue either has to do with:
OmniAuth not issuing the request_visible_actions to Google correctly
Me not setting the token in the Google::APIClient object properly
I've gotten this far using the following resources, but I'm officially stuck:
http://blog.baugues.com/google-calendar-api-oauth2-and-ruby-on-rails
https://developers.google.com/+/api/latest/moments/insert
Any ideas would be appreciated!
Here is my working code from web app using 'omniauth-google-oauth2' along with 'google-api-client'. This sample code uses calendar API, but I guess it will work for you.
require 'google/api_client'
class Calendar
def initialize(user)
#user = user
end
def events
result = api_client.execute(:api_method => calendar.events.list,
:parameters => {'calendarId' => 'primary'},
:authorization => user_credentials)
result.data
end
private
def api_client
#client ||= begin
client = Google::APIClient.new(application_name: 'xxx', application_version: '0.0.1')
client.authorization.client_id = ENV["GOOGLE_KEY"]
client.authorization.client_secret = ENV["GOOGLE_SECRET"]
client.authorization.scope = 'https://www.googleapis.com/auth/calendar'
client
end
end
def calendar
#calendar ||= api_client.discovered_api('calendar', 'v3')
end
def user_credentials
auth = api_client.authorization.dup
# #user.credentials is an OmniAuth::AuthHash cerated from request.env['omniauth.auth']['credentials']
auth.update_token!(access_token: #user.credentials.token)
auth
end
end
In the API console, did you register as a Web application or installed application?
I think for your case you must choose the installed application, so that the token is valid if user is not online.
Try changing https://www.googleapis.com/auth/plus.login with just plus.login. Its working for me with the same setup.

Ruby/HTTParty: Timing out when adding feed to Google Reader using its API

I'm attempting to add a subscription to Google Reader, using it's API, however I'm getting the following error:
execution expired
I've had no problems reading (using 'get') a list of subscriptions or tags. But it times out when I attempt to add a sub (using 'post')
The code is written in Ruby on Rails and I'm using HTTParty to handle the communication with the web service.
My code is as follows (I'm still new to Ruby/Rails so sorry for any bad practices included below. I'm more than happy to have them pointed out to me):
class ReaderUser
# Include HTTParty - this handles all the GET and POST requests.
include HTTParty
...
def add_feed(feed_url)
# Prepare the query
url = "http://www.google.com/reader/api/0/subscription/quickadd?client=scroll"
query = { :quickadd => feed_url, :ac => 'subscribe', :T => #token }
query_as_string = "quickadd=#{CGI::escape(feed_url)}&ac=subscribe&T=#{CGI::escape(#token.to_s)}"
headers = { "Content-type" => "application/x-www-form-urlencoded; charset=UTF-8", "Content-Length" => query_as_string.length.to_s, "Authorization" => "GoogleLogin auth=#{#auth}" }
# Execute the query
self.class.post(url, :query => query, :headers => headers)
end
...
end
For reference, this is how I'm obtaining the token:
# Obtains a token from reader
# This is required to 'post' items
def get_token
# Populate #auth
get_auth
# Prepare the query
url = 'http://www.google.com/reader/api/0/token'
headers = {"Content-type" => "application/x-www-form-urlencoded", "Authorization" => "GoogleLogin auth=#{#auth}" }
# Execute the query
#token = self.class.get(url, :headers => headers)
end
# Obtains the auth value.
# This is required to obtain the token and for other queries.
def get_auth
# Prepare the query
url = 'https://www.google.com/accounts/ClientLogin'
query = { :service => 'reader', :Email => #username, :Passwd => #password }
# Execute the query
data = self.class.get(url, :query => query)
# Find the string positions of AUTH
auth_index = data.index("Auth=") + 5
# Now extract the values of the auth
#auth = data[auth_index,data.length]
end
I'd be happy to provide any additional information required.
Thanks in advance!
After a great deal of messing around, I've found the solution!
I simply had to set the Content-Length to "0". Previously I was setting it to the length of the 'query' as per the PHP class I was basing it on (greader.class.php). I mention this just in case someone else has the same problem.

Resources