Validating user's API credentials - Rails, ActiveMerchant, and PayPal Express Gateway - ruby-on-rails

I'm building a marketplace application that uses PayPal Express. I've got a form for sellers to input their PayPal API credentials, but I need a way to validate them by making some sort of call to PayPal.
I'm using the PaypalExpressGateway in ActiveMerchant, and I don't see anything other than the standard purchase controls. Is there any sort of null-operation that can be used?
Any help would be greatly appreciated!

I am using the TransactionSearch operation for this purpose. By specifying STARTDATE=2100-01-01 00:00:00 it basically results in a no-op.
It will validate the credentials for you, without requiring any additional input from the seller.

For security reasons there isn't a way to check if the email is a valid paypal account. You can always make a small transaction and then void it if account validity is really required.

I don't have the answer personally. But I know Ryan Bates of Railscasts.com has recently devoted six (!) episodes to ActiveMerchant and Paypal in particular. Check out episodes #141 through #146 at railscasts.com.

Ok, after 4 hours...
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaypalExpressGateway < Gateway
def get_balance(options = {})
commit 'GetBalance', build_get_balance_request(options)
end
private
def build_get_balance_request(options)
xml = Builder::XmlMarkup.new :indent => 2
xml.tag! 'GetBalanceReq', 'xmlns' => PAYPAL_NAMESPACE do
xml.tag! 'GetBalanceRequest', 'xmlns:n2' => EBAY_NAMESPACE do
xml.tag! 'n2:Version', API_VERSION
xml.tag! 'n2:ReturnAllCurrencies', '1'
end
end
xml.target!
end
end
end
end
class SellerMerchantValidator < ActiveModel::Validator
def validate(record)
paypal_attrs = ['paypal_api_username', 'paypal_api_password', 'paypal_api_signature']
if record.paypal_merchant? && (record.changed - paypal_attrs).size < record.changed.size # one of paypal_attrs changed
response = record.gateway.get_balance
unless response.params['balance'].present?
record.errors[:base] << "Please check the PayPal details and make sure all three are entered correctly."
end
end
end
end
Thanks to Neils for the idea to check the TransactionSearch.
Please let me know if there is a better way to check if any of the api field changed.

There is also a call for GetBalance in the API.
Some sample code
Looks like the simplest (and quickest?) way.

Right, so if you want to test a user's credentials using ActiveMerchant, use the transaction_search method on the gateway
https://github.com/Shopify/active_merchant/blob/cb72e0f9c58f57b1293e6e976229b26cfbfee6a8/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb
This example will return a success (make sure to fill in your test credentials)
#username = ''
#password = ''
#signature = ''
gateway = ActiveMerchant::Billing::PaypalExpressGateway.new(
login: #username,
password: #password,
signature: #signature,
test: true
)
gateway.transaction_search({start_date: DateTime.now})

PayPal does have an AddressVerify API. It confirms whether a postal address and postal code match those of the specified PayPal account holder. I'm in the process of implementing it on our website right now, in fact.
You can read more about it here:
https://www.x.com/docs/DOC-1162#id0862M0QH02L
and here:
https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_AddressVerify

Related

Is there a way to restrict access to certain applications by specific users?

I have create several applications that communicate with our central auth server via doorkeeper. I want to make some applications accessible/inaccessible for specific users.
Is there a way to restrict access to specific oauth_applications and return a 401?
I believe the easiest way to achieve this would be the following:
In your doorkeeper application, change the Users table to include a permissions relationship. Something like, User -> has many -> permissions
And those permissions could contain just the name of the application you want to give them access to, (Or the ID of the application, you choose)
Then, in your config/initializer/doorkeeper.rb - inside Doorkeeper::JWT.configure - you add which applications that particular user can access inside the token payload, something like:
token_payload do |opts|
...
token[:permissions] = user.permissions.pluck(:application_name)
end
If you are using Doorkeeper without JWT, you can still pass extra information to the token by prepending a custom response to the ResponseToken object like so:
Doorkeeper::OAuth::TokenResponse.send :prepend, CustomTokenResponse
and CustomTokenResponse just need to implement the methods body, like so:
module CustomTokenResponse
def body
additional_data = {
'username' => env[:clearance].current_user.username,
'userid' => #token.resource_owner_id # you have an access to the #token object
# any other data
}
# call original `#body` method and merge its result with the additional data hash
super.merge(additional_data)
end
end
extra information can be found in Doorkeepers' wiki: https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-Token-Response
and in the Doorkeeper JWT gem: https://github.com/doorkeeper-gem/doorkeeper-jwt#usage
On 9 Feb 2020 a new configuration option was introduced in Doorkeeper to exactly do this.
Therefore, you can configure config/initializer/doorkeeper.rb:
authorize_resource_owner_for_client do |client, resource_owner|
resource_owner.admin? || client.owners_whitelist.include?(resource_owner)
end
I wanted the same behaviour. I use the resource_owner_authenticator block in config/initializer/doorkeeper.rb. When a user has one or more groups which are connected with an Oauth application it can continue.
rails g model UserGroup user:references group:references
rails g model GroupApplications group:references oauth_application:references
resource_owner_authenticator do
app = OauthApplication.find_by(uid: request.query_parameters['client_id'])
user_id = session["warden.user.user.key"][0][0] rescue nil
user = User.find_by_id(user_id)
if !app && user
user
elsif app && user
if !(user.groups & app.groups).empty?
user
else
redirect_to main_app.root_url, notice: "You are not authorized to access this application."
end
else
begin
session['user_return_to'] = request.url
redirect_to(new_user_session_url)
end
end
end

How can I connect Aweber to my Rails app using OAuth?

I'm trying to integrate my Rails app with Aweber via OAuth, using the official aweber gem.
If I follow their flow in the Rails console, I can get an access token, no problems:
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
puts oauth.request_token.authorize_url
# => https://auth.aweber.com/1.0/oauth/authorize?oauth_token=xxxxxxxxxxxxxx
Then I visit that URL, type in my credentials, get a verification code, and go back to the rails console:
oauth.authorize_with_verifier 'xxxxxx'
# => #<OAuth::AccessToken>
Success!
The problem is, I want to do this in the real world, not just at the console, which means my Ruby code needs to be broken up into two separate actions. First, there's the controller action which redirects to Aweber's Oauth page:
def aweber
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
redirect_to oauth.request_token(oauth_callback: "http://127.0.0.1:3000/auth/aweber/callback").authorize_url
end
Then there's the action which gets the access token after the user has input their credentials and been redirected:
def aweber_callback
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_verifier(params[:oauth_verifier])
end
When I do it this way, the final line (authorize_with_verifier) always raises #<OAuth::Unauthorized: 401 Unauthorized>.
Seems like the problem is that I'm initializing the oauth variable twice, meaning I have two unrelated instances of AWeber::Oauth ... and only the instance of AWeber::Oauth that generated the authorize_url can get the access token. But I can't get the same instance in both aweber_callback and aweber because I'm dealing with two completely different threads and instances of the controller.
When I inspect oauth, I can see that the internal variables oauth.request_token.params["oauth_token"] and oauth.request_token.params["oauth_token_secret"] are different in each oauth, which I'm guessing is the cause of the problem. I can get the 'correct' oauth_token from the params (params[:oauth_token]), but I can't figure out how to get the correct oauth_token_secret (not to mention that manually setting instance variables like this feels very hacky and is probably not the best approach.)
How can I generate an access token?
I finally got this working by storing the oauth_token_secret in the session. (And I have to say, I'm very unimpressed by Aweber's documentation and API setup. This took 10 times longer than it should have.)
Gemfile
gem 'aweber', '~> 1.6.1', require: "aweber"
Routes
get "auth/aweber", to: "integrations#aweber", as: :aweber
get "auth/aweber/callback", to: "integrations#aweber_callback", as: :aweber_callback
Integrations Controller
def aweber
oauth = get_aweber_oauth
request_token = oauth.request_token(oauth_callback: aweber_redirect_uri)
session[:aweber_oauth_token_secret] = request_token.secret
redirect_to request_token.authorize_url
end
def aweber_callback
oauth = get_aweber_oauth
oauth.request_token = OAuth::RequestToken.from_hash(
oauth.consumer,
oauth_token: params[:oauth_token],
oauth_token_secret: session[:aweber_oauth_token_secret],
)
access_token = oauth.authorize_with_verifier(params[:oauth_verifier])
# TODO save access_token.token and access_token.secret
end
private
def get_aweber_oauth
AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
end
def aweber_redirect_uri
#_aweber_callback_uri ||= begin
if Rails.env.production?
redirect_host = "http://myproductionurl.com"
else
redirect_host = "http://127.0.0.1:3000"
end
"#{redirect_host}#{Rails.application.routes.url_helpers.aweber_callback_path}"
end
end
The next step is to store access_token.token and .secret in my DB,
then I'll be able to authorize users on future requests like this:
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_access(current_user.aweber_token, current_user.aweber_secret)
aweber = AWeber::Base.new(oauth)
# Make calls using "aweber"...
I tried using the gem omniauth-aweber in combination with the omniauth gem, but I couldn't get it working (which is a shame, because I'm using other omniauth-xxx gems in this app and it would have been nice to keep things consistent.) Basically, that gem automatically handles the /auth/aweber part of the process, but after it redirects me back to /auth/aweber/callback/ I can't see any way to get the oauth_token_secret - it's not in the request params, the session, or the cookies.
I've answered my own question now but I'll give the bounty to anyone who can come up with an obvious improvement on the above, or figure out a way to make it all work with omniauth-aweber.
Reading through the AWeber API Ruby Library, this bit stands out
What if I don’t want to verify every time?
After verifying once, the oauth object contains an
oauth.access_token.token and and oauth.access_token.secret which may
be used to authorize your application without having to verify via
url:
... oauth.authorize_with_verifier('verification_code') puts 'Access
token: ' + oauth.access_token.token puts 'Access token secret: ' +
oauth.access_token.secret The token and secret can then be saved, and
authorization can be performed as follows:
require 'aweber'
oauth = AWeber::OAuth.new('consumer_key', 'consumer_secret')
#Rather than authorizing with the verification code, we use the token and secret
oauth.authorize_with_access(YOUR_ACCESS_TOKEN, YOUR_ACCESS_TOKEN_SECRET)
aweber = AWeber::Base.new(oauth)
So let's run through this:
You can create a class that keeps an object in memory for each User for enough time to finish the sign in and then save the token and secret for use until they expire.
Please note current_user is meant to be anything that uniquely identifies the user. You could use the session ID if your users aren't logged in yet at this point
class AWeberSignIn
def self.start_signing user
oauth = Rails.cache.fetch("#{user}/aweber", expires_in: 5.minutes) do
AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
end
oauth.request_token(oauth_callback: "http://127.0.0.1:3000/auth/aweber/callback").authorize_url
end
def self.authorize_with_verifier user, oauth_verifier
oauth = Rails.cache.fetch("#{user}/aweber")
oauth.authorize_with_verifier(oauth_verifier)
[oauth.access_token.token, oauth.access_token.secret]
end
def self.get_base_from_token token, secret
oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_access(token, secret)
AWeber::Base.new(oauth)
end
end
With this class, your controller methods become:
def aweber
redirect_to AWeberSignIn.start_signin current_user #Assuming you have a current_user helper. Use whatever gives you a unique value per user
end
def aweber_callback
token, secret = AWeberSignIn.authorize_with_verifier(current_user, params[:oauth_verifier])
#Do something with token and secret. Maybe save it to User attributes?
#You can then use them to get a AWeber base object via AWeberSignIn.get_base_from_token token, secret
end
Please note that this is using low-level Rails caching. Make sure you set up your caching technique if you want something different from the default

request for user out of office settings from exchange

I'm writing rails app with MS Exchange integration,
I use gem 'exchanger' in my application.
As I see - it's easy to get information about user availability
But how can I get information about is user available or not at current time?
(something like Out of office badge).
Is a some way make a request to exchange for user OutOfOffice status and how can I do it using exchanger gem?
Please, help.
I don't know the 'exchanger' specifics, but the operation you want is GetUserOofSettings.
Your exchange user need to have a permissions to use this API.
I add new class to operations in exchanger gem to processing GetUserOofRequest:
class Request < Operation::Request
attr_accessor :email_address
# Reset request options to defaults.
def reset
#email_address = 'test.test#test.com'
end
def to_xml
Nokogiri::XML::Builder.new do |xml|
xml.send("soap:Envelope", "xmlns:xsi" => NS["xsi"], "xmlns:xsd" => NS["xsd"], "xmlns:soap" => NS["soap"]) do
xml.send("soap:Body") do
xml.send("GetUserOofSettingsRequest", "xmlns" => NS["m"]) do
xml.send("Mailbox", "xmlns" => NS["t"]) do
xml.send("Address", #email_address)
end
end
end
end
end
end
end
end
Also you can get OOO user's status from GetUserAvailability API too
(just set range between start_time and end time to 1 hour).

Ruby open_id_authentication with Google OpenID

I am in my first steps of implementing OpenID in my Rails app.
open_id_authentication appeared to be a fairly easy-to-use plugin, which is why I decided to use it.
Logging in with my Google account seems to work perfectly, however I do not get the sreg/AX fields that I require.
My code is currently as follows:
class SessionsController < ApplicationController
def new; end
def create
open_id_authentication
end
protected
def open_id_authentication
authenticate_with_open_id(params[:openid_identifier], :required => ["http://axschema.org/contact/email"]) do |result, identity_url, registration|
if result.successful?
p registration.data
#current_user = User.find_by_identity_url(identity_url)
if #current_user
successful_login
else
failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
end
else
failed_login result.message
end
end
end
private
def successful_login
session[:user_id] = #current_user.id
redirect_to(root_url)
end
def failed_login(message)
flash[:error] = message
redirect_to(new_session_url)
end
end
I have already read various discussions about Google OpenID and all only say that you need to require the AX schema instead of the sreg field email, but even when I am doing so (as you can see in the code above), registration.data will remain empty ({}).
How do I effectively require the email from most OpenID providers with open_id_authentication?
The authenticate_with_open_id return the Sreg object, not the AX response. So you need instanciate this respone with Rack::OpenID::REPONSE like that :
ax_response = OpenID::AX::FetchResponse.from_success_response(request.env[Rack::OpenID::RESPONSE])
After you can fetch your data
ax_response['http://axschema.org/contact/email']
ax_response['http://axschema.org/namePerson/first']
ax_response['http://axschema.org/namePerson/last']
I've also stitched together a complete solution to Ruby on Rails 3, OpenID, and Google: http://blog.sethladd.com/2010/09/ruby-rails-openid-and-google.html
this post contains a good strategy to use AX for google and Sreg for others, to make this happen a little more seamlessly
http://www.franzens.org/2009/01/using-google-federated-login-in-your.html

How do I fake OpenID login in RSpec user story/Cucumber when using open_id_authentication plugin

I'm trying to write a Cucumber scenario that requires me to have a logged in user - that would normally be quite simple but I'm only using OpenID authentication (curtosy of the authentication plugin). However after digging through the open_id_authentication plugins guts I'm not sure how I could achieve this within Cucumber.
I've figured out a way, if you place this in your features/support/env.rb:
ActionController::Base.class_eval do
private
def begin_open_id_authentication(identity_url, options = {})
yield OpenIdAuthentication::Result.new(:successful), identity_url, nil
end
end
Then you can just do something like this in your appropriate step:
Given /^I am logged in as "(.*)"$/ do |name|
user = User.find_by_name(user)
post '/session', :openid_url => user.identity_url
# Some assertions just to make sure our hack in env.rb is still working
response.should redirect_to('/')
flash[:notice].should eql('Logged in successfully')
end
I'm just completely clobbering the open id auth for the cucumber features, obviously if I need instances where there is failed login I could do that based on the supplied identity_url.
If you want to be able to stub out responses do this:
In features/support/helpers.rb:
ActionController::Base.class_eval do
private
def fake_openid_response(identity_url)
[OpenIdAuthentication::Result.new(:successful), identity_url, nil]
end
def begin_open_id_authentication(identity_url, options = {})
yield fake_openid_response(identity_url)
end
end
By moving the response out to a separate method you can now stub the response in your steps if necessary. For example, if I wanted a :missing response and I had a controller GoogleLoginController I could do the following using Mocha:
GoogleLoginController.any_instance.stubs(:fake_openid_response)
.returns([OpenIdAuthentication::Result.new(:missing), identity_url, nil])
Bort, a rails skeleton app, has a full set of rspec tests and supports openID login so you may want to take a look and see what they do.
DEfusion's answer works except that I needed to normalize the identity_url like:
ActionController::Base.class_eval do
private
def begin_open_id_authentication(identity_url, options = {})
yield OpenIdAuthentication::Result.new(:successful), self.normalize_identifier(identity_url), nil
end
end
Thanks

Resources