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).
Related
My app is using Rails 3.0.4 and Devise 1.1.7.
I'm looking for a way to prevent users from sharing accounts as the app is a subscription based service. I've been searching for over a week, and I still don't know how to implement a solution. I'm hoping someone has implemented a solution and can point me in the right direction.
Solution (Thank you everyone for your answers and insight!)
In application controller.rb
before_filter :check_concurrent_session
def check_concurrent_session
if is_already_logged_in?
sign_out_and_redirect(current_user)
end
end
def is_already_logged_in?
current_user && !(session[:token] == current_user.login_token)
end
In session_controller that overrides Devise Sessions controller:
skip_before_filter :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save
end
In migration AddLoginTokenToUsers
def self.up
change_table "users" do |t|
t.string "login_token"
end
end
def self.down
change_table "users" do |t|
t.remove "login_token"
end
end
This gem works well: https://github.com/devise-security/devise-security
Add to Gemfile
gem 'devise-security'
after bundle install
rails generate devise_security:install
Then run
rails g migration AddSessionLimitableToUsers unique_session_id
Edit the migration file
class AddSessionLimitableToUsers < ActiveRecord::Migration
def change
add_column :users, :unique_session_id, :string, limit: 20
end
end
Then run
rake db:migrate
Edit your app/models/user.rb file
class User < ActiveRecord::Base
devise :session_limitable # other devise options
... rest of file ...
end
Done. Now logging in from another browser will kill any previous sessions. The gem actual notifies the user that he is about to kill a current session before logging in.
You can't do it.
You can control IP addresses of user, so you can prevent presence of user from two IP at a time. ANd you can bind login and IP. You can try to check cities and other geolocation data through IP to block user.
You can set cookies to control something else.
But none of this will guarantee that only one user uses this login, and that those 105 IP from all over the world doesn't belong to only one unique user, which uses Proxy or whatever.
And the last: you never need this in the Internet.
UPD
However, what I'm asking is about limiting multiple users from using the same account simultaneously which I feel should be possible
So you can store some token, that will contain some encrypted data: IP + secret string + user agent + user browser version + user OS + any other personal info: encrypt(IP + "some secret string" + request.user_agent + ...). And then you can set a session or cookie with that token. And with each request you can fetch it: if user is the same? Is he using the same browser and the same browser version from the same OS etc.
Also you can use dynamic tokens: you change token each request, so only one user could use system per session, because each request token will be changed, another user will be logged out as far as his token will be expired.
This is how I solved the duplicate session problem.
routes.rb
devise_for :users, :controllers => { :sessions => "my_sessions" }
my_sessions controller
class MySessionsController < Devise::SessionsController
skip_before_filter :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
application_controller
def check_concurrent_session
if duplicate_session?
sign_out_and_redirect(current_user)
flash[:notice] = "Duplicate Login Detected"
end
end
def duplicate_session?
user_signed_in? && (current_user.login_token != session[:token])
end
User model
Add a string field via a migration named login_token
This overrides the default Devise Session controller but inherits from it as well. On a new session a login session token is created and stored in login_token on the User model. In the application controller we call check_concurrent_session which signs out and redirects the current_user after calling the duplicate_session? function.
It's not the cleanest way to go about it, but it definitely works.
As far as actually implementing it in Devise, add this to your User.rb model.
Something like this will log them out automatically (untested).
def token_valid?
# Use fl00rs method of setting the token
session[:token] == cookies[:token]
end
## Monkey Patch Devise methods ##
def active_for_authentication?
super && token_valid?
end
def inactive_message
token_valid? ? super : "You are sharing your account."
end
I found that the solution in the original posting did not quite work for me. I wanted the first user to be logged out and a log-in page presented. Also, the sign_out_and_redirect(current_user) method does not seem to work the way I would expect. Using the SessionsController override in that solution I modified it to use websockets as follows:
def create
super
force_logout
end
private
def force_logout
logout_subscribe_address = "signout_subscribe_response_#{current_user[:id]}"
logout_subscribe_resp = {:message => "#{logout_subscribe_address }: #{current_user[:email]} signed out."}
WebsocketRails[:signout_subscribe].trigger(signout_subscribe_address, signout_subscribe_resp)
end
end
Make sure that all web pages subscribe to the signout channel and bind it to the same logout_subscribe_address action. In my application, each page also has a 'sign out' button, which signs out the client via the devise session Destroy action. When the websocket response is triggered in the web page, it simply clicks this button - the signout logic is invoked and the first user is presented with the sign in page.
This solution also does not require the skip_before_filter :check_concurrent_session and the model login_token since it triggers the forced logout without prejudice.
For the record, the devise_security_extension appears to provide the functionality to do this as well. It also puts up an appropriate alert warning the first user about what has happened (I haven't figured out how to do that yet).
Keep track of uniq IPs used per user. Now and then, run an analysis on those IPs - sharing would be obvious if a single account has simultaneous logins from different ISPs in different countries. Note that simply having a different IP is not sufficient grounds to consider it shared - some ISPs use round-robin proxies, so each hit would necessarily be a different IP.
While you can't reliably prevent users from sharing an account, what you can do (I think) is prevent more than one user being logged on at the same time to the same account. Not sure if this is sufficient for your business model, but it does get around a lot of the problems discussed in the other answers. I've implemented something that is currently in beta and seems to work reasonably well - there are some notes here
I'm implementing a custom payment gateway in Spree 2.2. It's one of those gateways where you redirect to the gateway's own website to take payment, and then the bank redirects back to you with a bunch of get params.
I'm having an issue where the order's payment_state and shipment_state end up as null in the database, despite the fact that they are not null in the order object itself, if I put a debugger in the code. Calling order.save doesn't seem to help.
I've implemented a dirty hack to workaround it:
# This is a hack - for some reason the payment_state and shipment_state weren't being persisted
# and where being stored in the database as null. Really the spree checkout process
# should take care of this and we shouldn't have to set them manually.
# We must be doing something wrong...
order.update_attribute :payment_state, 'paid'
order.update_attribute :shipment_state, 'ready'
But I'd really like to know what the actual issue is - why aren't those states being persisted? (I should add, before I call the code above, the values for order.payment_state and order.shipment_state respectively are balance_due and pending - but that's another issue. If I can get them to save in any way, that's the main issue.
Any ideas what I'm doing wrong?
Full code for my controller and gateway is below.
class Spree::CommBankController < Spree::StoreController
def secure_payment
order = current_order
#order_info = 'Espionage Online order ' + order.number
payment_params = {
"Title" => 'Espionage Online',
"vpc_AccessCode" => payment_method.preferred_access_code,
"vpc_Amount" => (order.total * 100).to_i, # convert to cents
"vpc_Desc" => #order_info,
"vpc_MerchTxnRef" => order.number,
"vpc_Merchant" => payment_method.preferred_merchant_id_no,
"vpc_OrderInfo" => #order_info,
"vpc_ReturnURL" => secure_payment_callback_url(payment_method_id: payment_method.id),
}
payment_request = ::CommWeb::PaymentRequest.new(payment_params, payment_method.preferred_secure_secret)
redirect_to payment_request.url
end
def secure_payment_callback
# Next line - see http://stackoverflow.com/questions/4116545/how-do-i-get-only-the-query-string-in-a-rails-route
order = current_order
query_params = params.except(*request.path_parameters.keys)
payment_response = ::CommWeb::PaymentResponse.new(query_params, payment_method.preferred_secure_secret)
if !secure_hash_matches?(payment_response)
flash.notice = 'Error with payment - secure hash did not match. Please try again.'
redirect_to checkout_state_path(order.state)
return
end
payment = order.payments.create!({
:source => Spree::CommbankCheckout.create({
:params_hash => payment_response.params.to_s,
:success => payment_response.success?,
:desc => payment_response.description,
:trx_response_code => payment_response.trx_response_code,
:message => payment_response.message,
}),
:amount => order.total,
:payment_method => payment_method,
:response_code => payment_response.trx_response_code,
})
payment.pend
if payment_response.success?
# Set payment to completed after order.next because
# spree expects at least one incomplete payment to process an order to complete
order.next!
payment.complete
debugger
# This is a hack - for some reason the payment_state and shipment_state weren't being persisted
# and where being stored in the database as null. Really the spree checkout process
# should take care of this and we shouldn't have to set them manually.
# We must be doing something wrong...
order.update_attribute :payment_state, 'paid'
order.update_attribute :shipment_state, 'ready'
else
payment.failure
end
if order.complete?
flash.notice = Spree.t(:order_processed_successfully)
session[:order_id] = nil
redirect_to completion_route(order)
else
flash.notice = 'Error: ' + payment_response.message + '. Please try again.'
redirect_to checkout_state_path(order.state)
end
end
def secure_hash_matches? payment_response
payment_response.secure_hash_matches?
end
def payment_method
#payment_method ||= Spree::PaymentMethod.find(params[:payment_method_id])
end
def completion_route(order)
order_path(order)
end
end
and the gateway...
# Partly inspired from https://github.com/spree-contrib/spree-adyen (the hosted payment page class)
module Spree
class Gateway::CommBank < Gateway
preference :merchant_id_no, :string
preference :access_code, :string
preference :secure_secret, :string
def auto_capture?
true
end
# Spree usually grabs these from a Credit Card object but when using
# Commbank's 3 Party where we wouldn't keep the credit card object
# as that's entered outside of the store forms
def actions
%w{capture}
end
# Indicates whether its possible to void the payment.
def can_void?(payment)
!payment.void?
end
# Indicates whether its possible to capture the payment
def can_capture?(payment)
payment.pending? || payment.checkout?
end
def method_type
'commbank'
end
def capture(*args)
ActiveMerchant::Billing::Response.new(true, "", {}, {})
end
def source_required?
false
end
def provider_class
self.class
end
def provider
self
end
def purchase
# This is normally delegated to the payment, but don't do that. Handle it here.
# This is a hack copied from the Spree Better Paypal Express gem.
Class.new do
def success?; true; end
def authorization; nil; end
end.new
end
end
end
Check order.state_changes. Do they show changes to the two states?
I am encountering the same issue while using "spree-adyen". The order.state_changes shows that the payment_state and shipment_state have changed to ready. However, it doesn't persist in the order. This happens randomly with 10% of the orders. I am currently calling order.update! manually on the such order, but would really like to know as well what the issue is.
Also, I am not quite sure if order.update! is a good solution, as it executes a lot of queries and can be very expensive.
Umm. So apparently order.update! will solve my issue. Woops.
Still, a call to order.update! isn't something I've seen in other Spree Payment Gateway gems (https://github.com/spree-contrib/better_spree_paypal_express or https://github.com/spree-contrib/spree-adyen), so I'd be interested to know if I'm doing something really stupid. (I ended up noticing it in the code of https://github.com/coinbase/coinbase-spree/blob/master/app%2Fcontrollers%2Fspree%2Fcoinbase_controller.rb)
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.
I'm thinking about writing an automatic spam protection system (maybe I will write a public gem) for rails.
My concept is to include a helper method in application_controller f.e.:
class ApplicationController < ActionController::Base
automatic_captcha_redirect(:min_time => 30.seconds :limit => 50)
...
end
Then I want to include automatical a before_filter in every controller, which checks, if the current request is via post, put or delete-method.
If the user's last post-request is smaller than :min_time, then the request should be redirected to an captcha-input-page (the posted user-data resides in hidden html fields).
# before_filter :check_spam
def check_spam
if !request.get? && session[:last_manipulation_at]
&& session[:last_manipulation_at] >= DateTime.now - 30.seconds
redirect_to captcha_path
# (doesn't know yet how to handle the post data to
# display in hidden fields in the spam-captcha-form)
end
end
And in captcha.haml
=form_tag
-request.params.each do |key, value|
=hidden_field_tag key, value
=captcha_image
=submit_button_tag
If the user submits the right captcha-word, his data will be posted to the right action.
Do you think thats realizable?
Any critics or suggestions? Or an idea how to realize this behaviour?
EDIT:
this should not pass through all the ActiveRecord stack; can't it be implemented as a middleware hook (Rails Rack)?
Yes, would be a good idea - but I'm not very familiar with rails rack :/
what about file uploads? (you can not store it in a hidden file)
Hm... maybe a check if there is a file in the post? (How could that be realized?)
what about Ajax posting?
Maybe sending back http-status codes (f.e. 503 Service temporary unavailable)
why only POST and not also PUT and DELETE?
corrected this in my question
EDIT:
First structure of processing (as non rack-app - I dont know how to write rack apps):
0) Settings in environment.rb
auto_recaptcha[:limit] = 10
auto_recaptcha[:min_time] = 1.minute
1) User posts data
Check last_manipulation and max. amount of allowed manipultations in application_controller.rb
class ApplicationController < ActionController::Base
before_filter :automatic_captcha_redirect
def automatic_captcha_redirect
session[:last_manipulation_at][:manipultation] = [] unless session[:last_manipulation_at][:manipultation]
# Checks if requests are falling under the specifications for showing captcha
if !request.get?
&& session[:last_manipulation_at][:date] > DateTime.now - auto_recaptcha[:min_time]
&& session[:last_manipulation_at][:manipultation].count < auto_recaptcha[:limit]
# If user answered captcha, verify it
if !verify_captcha(params)
#url = request.url
#params = request.params
render "layouts/captcha.haml"
else
# Add successfull manipulation to counter
session[:last_manipulation_at][:manipultation] << DateTime.now
session[:last_manipulation_at][:date] = DateTime.now
end
end
end
end
captcha.haml
-form_tag #url do
-request.params.each do |key, value|
=hidden_field_tag key, value
=captcha_image
=submit_button_tag
2)
...
...
...
last) Post userdata to the right location
post(params) => users_path # path "/users" with method: post
First, i would like to say that this is a very good ideea of a feature.
My qs/remarks:
this should not pass through all the ActiveRecord stack; can't it be implemented as a middleware hook (Rails Rack)?
what about file uploads? (you can not store it in a hidden file)
what about Ajax posting?
why only POST and not also PUT and DELETE?
Anyway, i would be more interested to see the number of posts in last 5 mins, for example, that the date of the last request. I believe it is more relevant.
One way this could be put together:
Middleware/rails metal component that
monitors the requests and adds the
information to the rack session.
Controller helpers for before_filters
on things that might need captchas
View helpers for displaying the
captchas
You could make the captcha rate adjustable through the args passing mechanism of use
#config/environment.rb
config.middleware.use 'CaptchaMiddleware',:period=>5.minutes,:limit=>50,:captcha_url=>'/captcha'
Also, this should not rely on hidden form fields because a determined bot writer could just change the value they are posting to your server code.
Simple middleware example code(slightly better than a stab in the dark, but still)
class CaptchaMiddleware
def initialize app,options
#app = app
#options=options
end
def update_stats!
#session based,on account of laziness
session[:reqs] ||= []
session[:reqs].reject!{ |request| request < Time.now - #options[:period]}
session[:reqs] << Time.now
end
def over_limit?
session[:reqs].length > #options[:limit]
end
def call env
#env = env
if #env["REQUEST_METHOD"]!='GET'
update_stats!
if over_limit?
return [302,{"Location: #{options[:captcha_url]}"},'']
end
end
#app.call env
end
def session
#env["rack.session"]
end
end
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