Im struggling to replace the default :password strategy for the Padrino-Warden gem.
The way I figure it I need to get a hold of the manager instance for warden so I can set the default strategies but I'm not sure how to do that in the app.rb file
Right now app.rb looks like this
register Padrino::Warden
Warden::Strategies.add(:udid) do
def valid?
puts "udid strat"
params[:udid]
end
def authenticate!
user = User.get(:udid => params[:udid])
user.nil? ? fail!("Could not log in") : success!(user)
end
end
Warden::Manager.serialize_into_session do |user|
user.id
end
Warden::Manager.serialize_from_session do |id|
User.get(id)
end
use ::Warden::Manager do |manager|
manager.scope_defaults :default,
strategies: [:password],
action: 'sessions/unauthenticated'
end
Which does not work. The warden environment still looks like this
Warden::Proxy:70352196940440 #config={:default_scope=>:default, :scope_defaults=>{}, :default_strategies=>{:_all=>[:password]}, :intercept_401=>true, :failure_app=>Dagis}
If I configure warden in config.ru it will set the correct environment for warden but then I suspect the session middleware provided by Padrino does not work well together with Warden.
You need to instruct the warden manager when to use the strategy. Update the strategies key that you're passing to the use method to reflect which strategies you want to enable and the order in which they should be run. You could do this:
use ::Warden::Manager do |manager|
manager.scope_defaults :default,
strategies: [:udid, :password],
action: 'sessions/unauthenticated'
end
There's a bunch of examples on the Warden wiki on github
Related
You can set a session expiry for a Sinatra app when you set up the session engine:
use Rack::Session::Cookie, :expire_after => 60*60*3, :secret => 'xxxx'
But I want to enable a longer session for certain users. Say two weeks.
session[:expires] = ?? #how and where do I put this.?
Do I need to set on each call (before do?) or is once good enough? Is session[:expires] the right thing to set?
First make sure you don't set an expire_after value with the use Rack::Session::Cookie command, then put use Rack::Session::Cookie in your configure block. Next create an "expiry time" variable (let's say expiry_time for this example) or setting in config. Now for each user, when they login, retrieve their expiry_time setting and issue the following command:
env['rack.session.options'].merge! expire_after: expiry_time
That should do what you are asking.
If this doesn't work for you, try putting the env...merge! command in a before block.
I tried to do this via an after filter in Sinatra but it didn't work, I guess it sets the session after after filters have run, so I knocked up a quick Rack filter and it appears to work.
require 'sinatra'
class SessionExpiryModifier
def initialize(app,options={})
#app,#options = app,options
end
def call(env)
warn env["rack.session.options"].inspect
t = Time.now.to_i.even? ? 10 : 60
env["rack.session.options"].merge! :expire_after => 60 * 60 * t
#app.call env
end
end
configure do
use Rack::Session::Cookie,
:expire_after => 60*60*3,
:secret => 'xxxx' * 10
use SessionExpiryModifier
end
get "/" do
session[:usr] = Time.now
env["rack.session.options"].inspect
end
However, that makes it a lot harder to get a conditional from the Sinatra app into the Rack filter to decide on which branch to take, but that depends on what your condition is. Perhaps inject something into the headers that the filter can read to make the decision.
I am using sidekiq in my rails application.
By Default, Sidekiq can be accessed by anybody by appending "/sidekiq" after the url.
I want to password protect / authenticate only the sidekiq part. How can i do that?
Put the following into your sidekiq initializer
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(user), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_USER"])) &
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_PASSWORD"]))
end
And in the routes file:
mount Sidekiq::Web => '/sidekiq'
Sorry to late to the party, but Sidekiq's wiki recommends the following for Devise:
To allow any authenticated User:
# config/routes.rb
authenticate :user do
mount Sidekiq::Web => '/sidekiq'
end
To restrict access to User.admin?
# config/routes.rb
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => '/sidekiq'
end
This wiki post also has many other security schemes.
This was tested using Rails 5.1.3, Devise 4.3 and Sidekiq 5.0
See "Security" under https://github.com/mperham/sidekiq/wiki/Monitoring
Sidekiq::Web uses Rack::Protection to protect your application against typical web attacks (such as CSRF, XSS, etc). Rack::Protection would invalidate your session and raise Forbidden error if it finds that your request doesn't satisfy security requirements. One of the possible situations is having your application working behind a reverse proxy and not passing important headers to it (X-Forwarded-For,X-Forwarded-Proto). Such situation and solution could be found in this article and issue #2560...
If you're using Devise (or other Warden-based authentication), you can do this, supposing you have an AdminUser model in your app.
# config/routes.rb
# This defines the authentication constraint
constraint = lambda do |request|
request.env['warden'].authenticate!({ scope: :admin_user })
end
# This mounts the route using the constraint.
# You could use any other path to make it less obvious
constraints constraint do
mount Sidekiq::Web => '/sidekiq'
end
If you are rolling your own custom authentication, then you can use the below example which is referenced in the docs here.
# lib/admin_constraint.rb
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
user = User.find request.session[:user_id]
user && user.admin?
end
end
# config/routes.rb
require 'sidekiq/web'
require 'admin_constraint'
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
The accepted answer is good, but I think that it can be implemented more securely, as Sidekiq documentation mentions (it got edited to demonstrate the right solution after I posted).
To protect your app against timing attacks, use ActiveSupport::SecurityUtils.secure_compare.
See https://codahale.com/a-lesson-in-timing-attacks/
See https://thisdata.com/blog/timing-attacks-against-string-comparison/
Also, use & (do not use &&) so that it doesn't short circuit.
And finally, use digests to stop length information leaking (default of secure_compare in Active Support 5).
So, in an initializer file, typically in config/initializers/sidekiq.rb in Rails projects, depending of your version of Active Support/Rails, write the following.
Active Support 5+: Thanks to Rails PR #24510, parameters passed to secure_compare are going through Digest::SHA256.hexdigest by default.
require 'active_support/security_utils'
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
ActiveSupport::SecurityUtils.secure_compare(user, ENV["SIDEKIQ_ADMIN_USER"]) &
ActiveSupport::SecurityUtils.secure_compare(password, ENV["SIDEKIQ_ADMIN_PASSWORD"])
end
Active Support 4:
require 'active_support/security_utils'
require 'sidekiq'
require 'sidekiq/web'
Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
# Protect against timing attacks:
# - See https://codahale.com/a-lesson-in-timing-attacks/
# - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
# - Use & (do not use &&) so that it doesn't short circuit.
# - Use digests to stop length information leaking
ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(user),
::Digest::SHA256.hexdigest(ENV["SIDEKIQ_ADMIN_USER"])
) &
ActiveSupport::SecurityUtils.secure_compare(
::Digest::SHA256.hexdigest(password),
::Digest::SHA256.hexdigest(ENV["SIDEKIQ_ADMIN_PASSWORD"])
)
end
If you're using Sorcery for authentication, here's how to use Rails routes constraints to protect certain routes.
Copied here from the sorcery wiki for redundancy:
This tutorial shows how to use Rails routes constraints with Sorcery gem. Thanks to #anthonator for writing it!
First, define UserConstraint module that will be used for all constraints:
module RouteConstraints::UserConstraint
def current_user(request)
User.find_by_id(request.session[:user_id])
end
end
Then, having that module defined, you can specify specific constraint classes. In these examples, first route will work only if there's no user logged in, the second will work only for logged user who is an admin:
class RouteConstraints::NoUserRequiredConstraint
include RouteConstraints::UserConstraint
def matches?(request)
!current_user(request).present?
end
end
class RouteConstraints::AdminRequiredConstraint
include RouteConstraints::UserConstraint
def matches?(request)
user = current_user(request)
user.present? && user.is_admin?
end
end
Finally, you can add the constraints to the config/routes.rb:
MyApp::Application.routes.draw do
# other routes …
root :to => 'admin#dashboard', :constraints => RouteConstraints::AdminRequiredConstraint.new
root :to => 'home#welcome', :constraints => RouteConstraints::NoUserRequiredConstraint.new
end
Another option would be to add something like CanCan and special access based on roles.
Lets say I have an image at app/assets/images/privateimages/myrestrictedimage1.jpg
If I try to go directly to the image via url say with something like
http://localhost:5555/assets/privateimages/myrestrictedimage1.jpg
I am able to view the image.
I would like to have a way to inspect any http request to decide if the user is allowed access to it.
I know I can use before_filter in controllers to do some preprocessing before continuing onto any of the controller actions but I dont think this will help me because I need to be attempting to do a controller action for this to take effect.
I have heard I might be able to do it with a rake task but after much searching I haven't found anything like what I am trying to do. Perhaps I have to create a ruby gem to do this but I have no clue how to do this.
Can anyone point me in the right direction? Thanks.
I used Rack Middleware
The middleware class looks like this:
class MyChecker
def initialize(app)
#app = app
end
def call(env)
if (docheck)
#do stuff here such as check the path.
#For example #path = env['PATH_INFO'] and compare against your okay paths
#if youre good and are able to continue then
#app.call(env)
else
#redirect such as
[301, {"Location" => /somewhere, "Content-Type" => "text/html"}, []]
end
end
end
make sure to make your middleware visible by adding the following to application.rb
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib) #if MyChecker is located in lib othewise substitute lib with wherever you have your middleware class
config.middleware.use "MyChecker"
end
You want to look at Rack (not rake).
I'm looking to build a simple, RESTful notification system for an internal project leveraging Sinatra. I've used EventMachine channels in the past to subscribe/publish to events, but in all my previous cases I was using EventMachine directly.
Does anyone know if it's possible to create, subscribe, and publish to EventMachine channels (running in Thin) from a Sinatra application, or even from some Rack middleware for that matter?
Have a look at async_sinatra.
Basically, to make it possible to use EventMachine when running in Thin you need to make it aware that you want to serve requests asynchronously. The Rack protocol is synchronous by design, and Thin expects a request to be done when the handler returns. There are ways to make Thin aware that you want to handle the request asynchronously (see think_async for an example how), and async_sinatra makes it very easy.
Bryan,
You can use the em-http-request library (https://github.com/igrigorik/em-http-request), this will allow you to reference a specific EventMachine application running on either A. the same server, B. a different server, or C. wherever you want really.
require 'eventmachine'
require 'em-http-request'
require 'sinatra/base'
require 'thin'
class ServerClass < EventMachine::Connection
def initialize(*args)
# ruby singleton - store channel data in global hash
($channels ||= [])
end
def post_init
puts "initialized"
$cb.call("initialized");
end
def receive_data(data)
# got information from client connection
end
def channel_send(msg,channel)
$channels[channel].send_data(msg)
end
def channels_send(msg)
$channels.each{|channel| channel.send_data(msg)}
end
def unbind
# puts user left
end
end
EventMachine.run do
$cb = EM.callback {|msg| puts msg #do something creative}
$ems = EventMachine::start_server('0.0.0.0',ServerClass,args)
class App < Sinatra::Base
set :public, File.dirname(__FILE__) + '/public'
get '/' do
erb :index
end
end
App.run!({:port => 3000})
end
Above is a basic wireframe. Depending on how you want to go about sending data, you can use WebSockets (em-websocket) and bind each user on login (have to add a login system), or you can use this for whatever. As long as you have a global reference to the Eventmachine Object (connection, websocket, channel) you can pass messages from within your application.
BTW - It is optional to add the EventMachine.run do;....end loop, since Thin will do this anyways. It helps to know how it works though.
Good Luck
I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.
In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.
I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.
Here is a very simple authentication scheme for Sinatra.
I’ll explain how it works below.
class App < Sinatra::Base
set :sessions => true
register do
def auth (type)
condition do
redirect "/login" unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
#user != nil
end
end
before do
#user = User.get(session[:user_id])
end
get "/" do
"Hello, anonymous."
end
get "/protected", :auth => :user do
"Hello, #{#user.name}."
end
post "/login" do
session[:user_id] = User.authenticate(params).id
end
get "/logout" do
session[:user_id] = nil
end
end
For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.
The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)
Finally, the before handler sets up a #user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.
Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"You're welcome"
end
I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.
I' have found this tutorial and repository with a full example, its working fine for me
https://sklise.com/2013/03/08/sinatra-warden-auth/
https://github.com/sklise/sinatra-warden-example
I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'change_me'
As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.
I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.
Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/