How to pass arguments to initialize method from a class? - ruby-on-rails

I am using Push8 gem for Apple push notification which accepts .P8 certificates.The problem is that I have two bundle_id for two separate apps and need to send Push Notifications to both of them. The Push8 gem accepts bundle_id ENV['APN_BUNDLE_ID'] params automatically from the application.yml file. However, I want it to use ENV['APN_VENDOR_BUNDLE_ID'] as well for a different APP to send Push Notification.
my code to send Push notification is here
def self.send_notification_ios(device_id, notification_id)
send = Notification.where(id: notification_id).first
if Rails.env == 'development'
apn = P8push::Client.development
else
apn = P8push::Client.production
end
token = device_id
notification = P8push::Notification.new(device: token)
notification.alert = send.template.message % { txnid: send.order.txnid }
notification.sound = 'sosumi.aiff'
apn.push(notification)
end
Here If the send.end_user_type is "User" I want to use the Bundle id APN_BUNDLE_ID as the topic, for rest as want to use APN_VENDOR_BUNDLE_ID. But I dont know how to pass APN_VENDOR_BUNDLE_ID as a param to initialize method in the client.rb file of the gem. Hence it always accepts APN_BUNDLE_ID as the topic and hence throws the error topic disallowed.
Here is the client.rb file for the gem:
https://github.com/andrewarrow/p8push/blob/master/lib/p8push/client.rb
The link for the gem is https://github.com/andrewarrow/p8push

If the initialize method makes no accommodations for customizing that attribute you've got two choices: Monkey-patch it to make it do what you want, which is messy, or subclass it and use that instead.
The sub-class solution looks like this:
class UserAwareClient < P8Push::Client
def self.development(user_type)
client = self.new(user_type)
client.jwt_uri = APPLE_DEVELOPMENT_JWT_URI
client
end
def self.production(user_type)
client = self.new(user_type)
client.jwt_uri = APPLE_PRODUCTION_JWT_URI
client
end
def initialize(user_type)
# Initialize as the parent class would
super
# Then detect the user_type argument and decide how to configure it
#private_key =
case (user_type)
when 'User'
File.read(ENV['APN_PRIVATE_KEY'])
else
File.read(ENV['APN_VENDOR_BUNDLE_ID'])
end
end
end
end
Then you create that:
apn = UserAwareClient.development(user_type)
This gem could be made a lot more flexible with a few pull-requests to make your life easier, so consider that, too.

Related

Update attribute in associated model in Rails when parent is updated

I have two models User and Assignment. Whenever User is updated I want to update the url attribute in Assignment.
How do I do this?
class User
has_many :assignments
...
end
class Assignment
belongs_to :user
before_save :set_url
def set_sandbox_url
language = 'www'
project = 'example'
base_url = "https://#{language}.#{project}.org/"
sandbox_url = "#{base_url}/User:#{user.username}/#{article_title}"
end
I agree with Tamer Shlash that there is not really a benefit in storing this URL in the database because it can be easily generated each time you need it.
But apart from that, I would like to answer your question. Your callback to regenerate the URL doesn't work for various reasons. First, you want to update the URL when the user changes therefore the user needs to have a callback defined. Second, the naming is not correct. The callback as it is currently written would try to run a set_url method but the method is actually called set_sandbox_url. And third, sandbox_url = will just assign the new URL to a local variable sandbox_url but it would not update the instance variable #sandbox_url.
I would do something like this:
# in app/models/user.rb
after_save :update_assignment_url
private
def update_assignment_url
assignments.each(&:update_url) if username_previously_changed?
end
# in app/models/assignments.rb
def update_url
language = 'www'
project = 'example'
base_url = "https://#{language}.#{project}.org/"
sandbox_url = "#{base_url}/User:#{user.username}/#{article_title}"
update!(sandbox_url: sandbox_url)
end
Note: because you build the URL by simply concatenating strings I suggest making sure that these strings (especially values provided by the user like username and article_title) only include characters that are valid in an URL (for example by using String#parameterize).
You might want to read about Dirty Attributes too which provided the used username_previously_changed? method.

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

Drying up Rails controllers

I have an application in Ruby/Rails where I am going to have to connect to a third-party application (Xero accounting) in order to send/pull data. This happens in several controllers thoughout the application. However, whilst the specifics are different, the actual connection is the same, and looks like this:
require 'xero_gateway'
xero_config = YAML.load_file("#{Rails.root}/config/xero.yml")["testing"]
xero_private_key = "#{ENV['HOME']}/certs/breathehr_xero.key" || ENV["xero_private_key"]
xero_gateway = XeroGateway::PrivateApp.new(xero_config['consumer_key'], xero_config['consumer_secret'], xero_private_key)
The next step of the code might be fetch something, like an invoice, as in:
xero_gateway.get_invoice('INV-001')
Or, in another controller, it might be to create a contact, such as:
xero_gateway.build_contact
Is there somewhere I can put the connection code so that I can end up just calling xero_gateway in each specific controller? It doesn't feel right to be repeating the same code again and again each time I have to authenticate and connect.
To build on #Dave Newton's answer:
You would create a "provider" class that marries an object factory to some configuration:
File: lib/xero_gateway_provider.rb
require 'xero_gateway'
class XeroGatewayProvider
cattr_accessor :default_private_key, :default_consumer_key, :default_consumer_secret
def initialize(overrides = {})
#private_key = overrides[:private_key] || self.class.default_private_key
#consumer_key = overrides[:consumer_key] || self.class.default_consumer_key
#consumer_secret = overrides[:consumer_secret] || self.class.default_consumer_secret
end
def create_private_app
XeroGateway::PrivateApp.new(#consumer_key, #consumer_secret, #private_key)
end
end
Then you could create a Rails initializer: config/initializers/xero_gateway_provider.rb
require 'xero_gateway_provider'
conf = YAML.load_file("#{Rails.root}/config/xero.yml")[Rails.env]
XeroGatewayProvider.default_private_key = "#{ENV['HOME']}/certs/breathehr_xero.key" || ENV["xero_private_key"]
XeroGatewayProvider.default_consumer_key = conf["consumer_key"]
XeroGatewayProvider.default_consumer_secret = conf["consumer_secret"]
And to use it:
# Using default configs
provider = XeroGatewayProvider.new;
private_app = provider.create_private_app
private_app.get_invoice("...")
# Using overrides
provider = XeroGatewayProvider.new :consumer_key => '...', :consumer_secret => '...';
private_app = provider.create_private_app
private_app.get_invoice("...")
Edit: Just realized there is no point to instantiating XeroGatewayProvider if it uses class level properties, so I made them defaults allowing you to configure each provider individually.
Also #Gareth Burrows comment on where to put and name the class, I think this would fit just fine in the lib/ directory. See the edits to the post for specifics.
You could put it just about anywhere, including:
A utility class used by your actions, or
A base action class (meh), or
A module included in your actions (meh).
I lean towards a utility class, because:
It's easy to instantiate or mock/stub it for testing, and
It doesn't make the surface area of your action class any bigger
You can create a regular ruby class in the models folder called Xero or something and do this code in the initializer.
require 'xero_gateway'
class Xero
def initialize
xero_config = YAML.load_file("#{Rails.root}/config/xero.yml")["testing"]
xero_private_key = "#{ENV['HOME']}/certs/breathehr_xero.key" || ENV["xero_private_key"]
xero_gateway = XeroGateway::PrivateApp.new(xero_config['consumer_key'], xero_config['consumer_secret'], xero_private_key)
end
end
And then just call:
xero_gateway = Xero.new
Another option is to create an initializer in the initializers/ folder.
xero_gateway.rb
And put the initialization code in there. This way it will be parsed only on application startup.

stripe webhooks for rails 4

I have been trying to setup my first webhook with stripe. I found an article that looks like the right way to do it but 2 years old. I am thinking it is outdated.
Here is my controller so far.
class StripewebhooksController < ApplicationController
# Set your secret key: remember to change this to your live secret key in production
# See your keys here https://manage.stripe.com/account
Stripe.api_key = "mytestapikey"
require 'json'
post '/stripewebhooks' do
data = JSON.parse request.body.read, :symbolize_names => true
p data
puts "Received event with ID: #{data[:id]} Type: #{data[:type]}"
# Retrieving the event from the Stripe API guarantees its authenticity
event = Stripe::Event.retrieve(data[:id])
# This will send receipts on succesful invoices
# You could also send emails on all charge.succeeded events
if event.type == 'invoice.payment_succeeded'
email_invoice_receipt(event.data.object)
end
end
end
Will this work correctly and is this the right way to do it? Here is the stripe documentation.
I'm using Stripe Webhooks in production and this doesn't look quite right. You should first define your webhook URL in your routes like this:
# config/routes.rb
MyApp::Application.routes.draw do
post 'webhook/receive'
end
In this example your webhook url will be at http://yourapp.com/webhook/receive (that's what you give to Stripe). Then you need the appropriate controller and action:
class WebhookController < ApplicationController
# You need this line or you'll get CSRF/token errors from Rails (because this is a post)
skip_before_filter :verify_authenticity_token
def receive
# I like to save all my webhook events (just in case)
# and parse them in the background
# If you want to do that, do this
event = Event.new({raw_body: request.body.read})
event.save
# OR If you'd rather just parse and act
# Do something like this
raw_body = request.body.read
json = JSON.parse raw_body
event_type = json['type'] # You most likely need the event type
customer_id = json['data']['object']['customer'] # Customer ID is the other main bit of info you need
# Do the rest of your business here
# Stripe just needs a 200/ok in return
render nothing: true
end
end
Another thing to note: every webhook you receive has an ID. It's good practice to save and check against this to make sure you're not acting on the same event more than once.

How to setup default attributes in a ruby model

I have a model User and when I create one, I want to pragmatically setup some API keys and what not, specifically:
#user.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
I want to be able to run User.create!(:email=>"something#test.com") and have it create a user with a randomly generated API key, and secret.
I currently am doing this in the controller, but when I tried to add a default user to the seeds.rb file, I am getting an SQL error (saying my apikey is null).
I tried overriding the save definition, but that seemed to cause problems when I updated the model, because it would override the values. I tried overriding the initialize definition, but that is returning a nil:NilClass and breaking things.
Is there a better way to do this?
use callbacks and ||= ( = unless object is not nil ) :)
class User < ActiveRecord::Base
before_create :add_apikey #or before_save
private
def add_apikey
self.apikey ||= Digest::MD5.hexdigest(BCrypt::Password.create(self.password).to_s)
end
end
but you should definitely take a look at devise, authlogic or clearance gems
What if, in your save definition, you check if the apikey is nil, and if so, you set it?
Have a look at ActiveRecord::Callbacks & in particular before_validation.
class User
def self.create_user_with_digest(:options = { })
self.create(:options)
self.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
self.save
return self
end
end
Then you can call User.create_user_with_digest(:name => "bob") and you'll get a digest created automatically and assigned to the user, You probably want to generate the api key with another library than MD5 such as SHA256 you should also probably put some user enterable field, a continuously increasing number (such as the current date-time) and a salt as well.
Hope this helps
I believe this works... just put the method in your model.
def apikey=(value)
self[:apikey] = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
end

Resources