Supplying credentials to web service via ActiveResource - ruby-on-rails

This is my first attempt at writing a Rails app to consume an external web service.
I have done some basic experimentation retrieving some records from another rails app via ActiveResource which worked pretty well, but in this instance I am trying to access a third party web service which requires very specific authentication before I can do anything.
The service in question is a REST API provided by DNSMadeEasy the documentation for which can be located at http://www.dnsmadeeasy.com/services/rest-api/
The authentication requirements according to the documentation are as follows:
Create the string representation of the current date and time in HTTP format. Example:
Sat, 12 Feb 2011 20:59:04 GMT
Calculate the hexadecimal HMAC SHA1 hash of that string using your Secret key as the hash key.
Set the values for the request headers using your API key, the current date and time, and the HMAC hash that you calculated.
So I figured out how to get the date and calculate the hash:
request_date = Time.now.httpdate
hmac = OpenSSL::HMAC.hexdigest('sha1', secret_key, request_date)
So my question has three parts:
Firstly, how do I then go about inserting this information in the HTTP header when I send off my request to the web service?
Secondly, how do I go about putting all this in a super class that my ActiveResource classes inherit from, so that I don’t have to worry about it in the classes for Domain and ResourceRecord?
Thirdly, is there a best practice for storing API and secret keys in your app, i.e. should this be done with an initializer, or is it best to use an environment variable?
Any tips and tricks for this sort of workflow would be extremely appreciated.
Thanks

1 - You can set the headers by setting an instance variable called #headers like this:
class Base < ActiveResource::Base
#headers = { ‘key’ => ‘value’ }
end
Take a look at the source Active Resource: Headers
2 - Make all your resources inherit from that Base that you just created instead that making them inherit from ActiveResource::Base.
3 - I usually put those keys in environments files or I use this gem SettingsLogic

Related

Rails - Build adapter for several APIs

I need help on my thoughts on building an adapter for several APIs in Rails.
Let me explain first:
I want my clients to be able to add third-party extensions to my app.
I have a model Extension and another CustomExtension.
I create the extensions myself, that appear then to the "Available extensions" section. When a user clicks on "Add extension", it goes to the "My extensions" section (which is represented by CustomExtension).
I created an extension called API Connector and i want it to work as follows:
When a user clicks on "Add extension", he picks a category (that i have defined myself) the API is part of (like "Customer Reviews"). Then, the user will enter some fields like an api_key or an api_endpoint. Then, i want to be able to connect to this api and display some other fields relevant to the api (like the name of where it comes from, example: for Customer Reviews, if a user connects the Google API for it, i want to rename the extension from API Connector to Google Customer Reviews).
In a few words, I want to be able to connect several and different APIs with the same interface and let the user do it without implementing the API in my project.
EDIT — More info:
The APIs might not have the same authentication process or the same properties. They can be very different from each other.
The technical requirements are RESTful APIs and JSON-based.
As I understand it, you want to create a way for users to connect to APIs that are unknown until runtime, based on the parameters that a user defines? If so, there's a Ruby library (now removed from Rails) that's built for allowing easy connection to REST APIs, maybe that could be of help here?
https://github.com/rails/activeresource
So, suppose I want to pull breed info from the Cat API. Here's some example code that would let me define that at runtime:
require "active_resource"
require "ostruct"
##
# This is just a data-store. It could be an ActiveRecord object or some other set
# of values that you need for the API. You'll have to establish your own criteria
# for types of API configuration you can support
#config = OpenStruct.new(
# We need to set a custom header and value
header_name: 'x-api-key',
# get your own key here: https://docs.thecatapi.com
header_value: '96120fe6-0846-41c6-9a1d-8a70e691dd47',
base_url: "https://api.thecatapi.com/v1/",
# What's the path where this resource can be found
resource_name: "breeds",
# don't add ".json" to the URLs
use_json_extension: false,
)
##
# Create an anonymous class at runtime that inherits from ActiveResource::Base
#custom_extension = Class.new(ActiveResource::Base)
##
# Config our class based on the user-provided values.
#custom_extension.class_eval do
self.include_format_in_path = #config.use_json_extension
self.site = #config.base_url
self.headers[#config.header_name] = #config.header_value
self.element_name = #config.resource_name
# Log errors here
self.logger = Logger.new("./output.log")
end
puts #custom_extension.all.to_s
With any luck, that should download a list of cat breeds for you. Which should be enough to demonstrate the concept. The docs for ActiveResource can be found here: https://rubydoc.info/gems/activeresource
Be careful that you're not importing dangerous content from a source provided by a user!
Hopefully that's what you are looking for?

How to make a rails server wide object?

I am using the RedditKit gem and in order to access certain elements, I need to send a request to reddit api to create a "client" object. Below is my current logic:
## application_controller
before_action :redditkit_login
private
def redditkit_login
#client = RedditKit::Client.new ENV["reddit_username"], ENV["reddit_password"]
end
As you can see in my logic here, before EVERY REQUEST, I am subsequently making a new client object and then using that everywhere.
My question is, how do I only make one client object which can be used to serve ALL requests from anywhere?
My motive behind this is speed. For every request to server, I am making a new request to reddit and then responding to the original request. I want to have the client object readily available at all times.
You have a lot of options. A simple one would be to create a config/initializers/reddit_client.rb file and put in there:
RedditClient = RedditKit::Client.new ENV.fetch("reddit_username"), ENV("reddit_password")
(note I switched to ENV.fetch because it will error if the key is not found, which can be helpful).
You could also rename the file as app/models/reddit_client.rb. Although it's not really a model, that folder is also autoloaded so it should work as well.

How to set variables across models for a single request in rails?

The scenario: I need to give models access to API tokens stored in the session.
Background: I have an API-driven rails 3 application utilizing DataMapper(DM) and a DM adapter to interface with the API. Each DM model has a corresponding REST-ish API endpoint much like you get with rails scaffolding. The API requires various headers for requests, including API tokens, keys, ids etc. The headers have nothing to do with the requested data, they exist for authorization and tracking purposes only. A number of these tokens are stored in the session. I want a clean way to make these API headers available to any model during a request.
Possible solutions:
1. Passing session variables from the controller to the models
The obvious answer is passing the tokens in a hash or other object from the controller to the models. A controller action might have the following: #user = User.find(params[:id], api_headers).
The problem is needing to override any model method to accept the additional api_headers object. Not counting methods defined by Rails and DataMapper, there are hundreds of methods already defined in the application models that would need to be rewritten. So I'm ruling out a rewrite, and this also doesn't seem like a good solution since it would require overriding a ridiculous number of DM methods like the User#find example above.
2. Some metaprogramming hack
I could catch any ArgumentError's on DM's base class and check if the last argument is the api_headers object, then set the values as instance variables and invoke the requested method. This thought exercise already has me cringing at dealing with optional arguments etc. If given long enough I could probably create a functional Frankenstein that should get me fired but probably wouldn't.
3. Use a singleton (current preferred solution)
In the application controller set a before_filter to dump the session-stored API headers into a singleton ApiHeaders object. Then any model making an API request can get that singleton with the required API headers.
An additional after_filter* on the application controller would set all attributes to nil on the ApiHeaders singleton at the end of the request to prevent leaking headers between requests.
This is currently my preferred solution but I don't like that the API header values could potentially carry over into other requests if the after_filter doesn't get invoked. I don't know in which scenarios this might happen (in an application error perhaps?) which raises concerns. All I know is the values don't necessarily die with the request.
4. Custom code
Drop support of DataMapper and the custom API adapter and manually make all API calls, passing through all required API headers. Besides the fact I don't have time for this level of rewrite, why use a framework at all if you have to throw a huge chunk out to support a custom auth scheme?
Summary
What's the cleanest way to get these pesky API tokens from the session into the bowels of the application where they can be sent with each API request? I'm hoping for a better solution than those listed above.
* An alias for after_action
I set the current user and the request information on my User model using the request_store gem which is just a tiny shim over thread local storage with a bit of clean-up.
This makes the information available from any of my models via the User class. I have User.current, User.request and User.location available wherever I need it.
Your controller just has to set User.current and User.request once it has authenticated the user.
Example User model:
# models/user.rb
require 'request_store'
class User
def self.current
RequestStore.store[:current_user]
end
def self.current=(user)
RequestStore.store[:current_user] = user
end
def self.request
RequestStore.store[:current_request]
end
def self.request=(request)
# stash the request so things like IP address and GEO-IP based location is available to other models
RequestStore.store[:current_request] = request
end
def self.location
# resolve the location just once per request
RequestStore.store[:current_location] ||= self.request.try(:location)
end
end
Use Thread.current, which is passed in from request to model (note, this breaks if, inside your request, you use sub-threads). You can store the attribute you want to share in a cattr_accessor or in rails cache:
in a cattr_accessor
class YourClass
cattr_accessor :my_var_hash
...
# and in your controller
# set the var
YourClass.my_var_hash = {} if YourClass.my_var_hash.nil?
YourClass.my_var_hash[Thread.current.object_id] = {}
YourClass.my_var_hash[Thread.current.object_id][your_var] = 100
... and in your model
lvalue = YourClass.my_var_hash[Thread.current.object_id][your_var]
Note, if you use this method, you will also want to make one of the hash values a timestamp, and do some housekeeping on getting, by deleting old keys, b/c you'll eventually use up all your system memory if you don't do the housekeeping
with cache:
# in your controller
#var = Rails.cache.fetch("#{Thread.current.object_id}_var_name") do
return 100 # do your work here to create the var value and return it
end
# in your model
lvalue = Rails.cache.fetch(("#{Thread.current.object_id}_var_name")
You can then set the cache expiration to 5 minutes, or you can wildcard clear your cache at the end of your request.

Generate XML dynamically and post it to a web service in Rails

I am currently developing a Rails app in which I need to dynamically send XML request to an external web service. I've never done this before and I a bit lost.
More precisely I need to send requests to my logistic partner when the status of an order is updated. For instance when an order is confirmed I need to send data such as the customer's address, the pickup address, etc...
I intended to use the XML builder to dynamically generate the request and Net:HTTP or HTTParty to post the request, based on this example.
Is that the right way to do so? How can I generate the XML request outside the controller and then use it in HTTParty or Net:HTTP?
Thanks for your help,
Clem
That method will work just fine.
As for how to get the XML where you need it, just pass it around like any other data. You can use the Builder representation, which will automatically convert to a String as appropriate, or you can pass around a stringified (to_s) version of the Builder object.
If, for example, it makes sense for your model (which we'll call OrderStatus) to generate the XML, and for your controller to post the request:
# Model (order_status.rb)
def to_xml
xml = Builder::XmlMarkup.new
... # Your code here
xml
end
# Controller (order_statuses_controller.rb)
def some_method
#order_status = OrderStatus.find(:some_criteria)
... # Your code here
http = Net::HTTP.new("www.thewebservicedomain.com")
response = http.post("/some/path/here", #order_status.to_xml)
end
You may want to wrap the HTTP calls in a begin/rescue/end block and do something with the response, but otherwise it's all pretty straightforward and simple.
Make XML with Builder, then send it down the wire.
In your case it sounds like you may need to send several different requests as the order evolves; in that case:
Plan out what your possible order states are.
Determine what data needs to be sent for each state.
Decide how to represent that state within your models, so you can send the appropriate request when the state changes.
Where my example uses one method to generate XML, maybe you'll want 5 methods to handle 5 possible order states.

How to deal with authentication for a Ruby API wrapper?

I'm working on an API wrapper for Viddler, which will eventually be made public, and I'm trying to figure out the best way to deal with authentication/API keys, specifically with usage within Rails applications in mind.
The easiest way to write the wrapper would be to just have the code create a new client each time, and the developer could store the API key in a constant for future use:
#client = Viddler::Client.new(VIDDLER_API_KEY)
The problem with this is, it's kind of clunky to have to keep creating client objects and passing in the API key. This gets even more complicated when you throw user authentication into the mix.
I'm thinking some sort of solution where I all the the API key to be set in the environment file and then the authentication would be done in a before_filter.
Viddler::Client.api_key = 'abc123'
Viddler::Client.authenticate! 'username', 'password'
Viddler::Client would then store this in a class variable, and you could call Viddler::Client.new without any parameters and make authenticated calls. One thing I'd be concerned about is that this means the developer would have to be sure to clear out the authentication before or after each request, since the class variables would persist between requests.
Any thoughts?
Storing the API key globally would for sure be pretty useful and certainly is the way to go for that kind of information. User authentication on the other hand I think shouldn't be stored globally, never ever, especially for a high level API, because telling your users to "ensure to add an after_filter :reset_viddler_auth" might lead to some unexpected security risks.
# in a config/initializer/*.rb file or something
Viddler::Client.api_key = "abc123"
# in the controller/action/model/wherever
#client = Viddler::Client.new # anonymous
#client.authenticate!("username", "password") # authenticate anon client
#client_auth = Viddler::Client.new("username", "password") # authenticated client
Guess like that you've best of both worlds :) Maybe even provide a way to create a new client with another API key like,
#client_other = Viddler::Client.new("username", "password", :api_key => "xyz890")
So... just my 2 cents.
PS: not sure how up-to-date it is, but there's already a ruby viddler wrapper, just FYI, http://viddler.rubyforge.org/rdoc/

Resources