So there I was, right. Just looking through some code, studying bits and pieces when all of a sudden, my ocular receptors were assaulted by the unfamiliar. I was like:
What!!! What's that all about!
Anyway, what I saw was (source):
def authenticate_user!
if doorkeeper_token
Thread.current[:current_user] = User.find(doorkeeper_token.resource_owner_id)
end
# ...
end
So after looking at it for a while, thinking:
Wtf is this Thread.current[] insanity? Is this even necessary? What's it even trying to do?
It seemed to me it was kind of like wearing a baseball cap backwards: You may look pretty f'ing cool but that sun glare is winning. I then decided to Google around, read some articles and some SO.
None seemed to concisely answer my question: Given the context of the code, would it not be the same as:
def authenticate_user!
if doorkeeper_token
#current_user = User.find(doorkeeper_token.resource_owner_id)
end
# ...
end
If not, what situation/scenario is it useful/protecting against?
I hope you enjoyed my story and want to contribute an awesome ending.
There are different bulletin boards on which you can pin information. The bulletin board where local variables are posted is hidden behind hedges and cannot be seen by the views who live in the next yard:
current_user = User.find(doorkeeper_token.resource_owner_id)
The bulletin board where #variables are posted is nailed to the top of a ladder, so the views have an unobstructed view of the #variables over the tops of the hedges:
#current_user = User.find(doorkeeper_token.resource_owner_id)
But other methods, which live in classes across the street, cannot see the #variables posted on the bulletin board on top of the ladder because a row of trees is in the way.
Thread variables, such as:
Thread.current[:current_user] = User.find(doorkeeper_token.resource_owner_id)
launch a kite, which flies higher than the trees, and the variables posted on the kite can be seen by the methods that live across the street.
Why not create a ruby global variable, e.g. $current_user, instead? Because then requests initiated simultaneously by different users will write to the same global variable, potentially screwing things up.
Given the context of the code
There's not enough context to tell why a (thread) global variable is needed.
From the author:
I use Thread to store current user to be able transparently
authenticate user and extend this later. Currently this code gives
priority to token-based authentication via Doorkeepr, and you can
extend it to any other algorithm. I am not a big fan of Devise and did
not want to use the token-based authentication strategy it provides.
Once you write user info inside Thread.currrent[:current_user], any
other service can read it independently and use for whatever purposes
it is needed. For example, as i mentioned above, checking
authorization, logging who did the events happening in the system, etc
as for writing it to #current_user, i am not sure it will be
accessible in all contexts. But i have not really checked this
Reference: https://github.com/rilian/devise-doorkeeper-cancan-api-example/issues/1#issuecomment-143479288
Related
I have used declarative authorization gems like cancan and cancancan in the past to control access to data. However, in this new application I am trying to control access to actual features.
It's your typical SaaS model, where progressively more features are available depending upon the level at which the user has subscribed.
Most of the features are available by clicking different icons or menu items throughout the application.
I'm writing to inquire whether there is a well-known implementation pattern for this that I'm not aware of before I roll my own.
Here are the different types of things that will be limited based upon the subscription level:
Features accessible by icon
Features accessible by menu item
Certain Reports (each of which has a ReportDefinition defining it.)
Certain Uploads (each of which has a FileType defining it.)
Certain BackgroundProcesses (each of which has a ProcessType defining it.)
Each Subscription has a Plan. It's simple enough to tie that Plan into items 3, 4, and 5 above and use cancancan for accessibility by current_user. However, features 1 and 2 are a different story. I can see wrapping their accessibility in a view helper which checks a Feature/Plan list. But that only handles the view. If the user knows the URL, they'd still be able to access the feature by typing the URL in directly. In that case, do I need to handle the authorization again at the controller action level, or is there instead some kind of middleware I could put in to limit accessibility to the features?
Thanks very much.
If it's a simple app, I simply add an admin column on User. If there's more than 2 user types (admin/non-admin/author/editor/etc) then I would make it an Enum field instead of boolean.
Then, inside user.rb I add several methods...
def is_admin?
admin?
end
def is_author?
!admin?
end
From there, I also add some code in application_controller.rb which raise an exception:
def unauthorized
head(:unauthorized)
end
def unprocessable
head(:unprocessable_entity)
end
I also add a current_user method in application_controller.rb:
helper_method :current_user
def current_user
#user ||= User.find(session[:user_id])
end
Then in my views, that's where I handle "hiding" things or disabling buttons.
<%= if current_user.is_admin? %>
<button>Admin button</button>
<% else %>
<button>Author button</button>
<% end %>
This of course is NOT security (view-layer only changes), which is why I also return early from controllers using the previous methods I laid out:
def destroy
return unauthorized unless current_user.is_admin?
# ... delete code here
end
For the example above dont forget to use return or the code will keep executing.
For things that are more than simple, I use Pundit.
I would simple roll my own using enum to set different access or subscription levels. Then just write a before_action called can_access? to block off entire actions. Then I would set some conditionals or view_helpers in the view to block access to certain UI elements.
https://github.com/jnunemaker/flipper is a good solution and does exactly what you are looking for.
Otherwise, like you said, cancancan is good for a naive solution.
Earlier it was working fine. I don't know what happen but I get this error now.
Rails.root: /Users/abhimanyuaryan/RubymineProjects/TwitterBy
The path in the error message is "/zombie" but all of your paths
use "/zombies"
you might want to modify the various id paths /zombies/:id(.format) to
be /zombie/id(.format). that's matches rails way of doing thing.
hth
As mentioned in the answer, your problem is that you're trying to access /zombie when the routes are either /zombies or /zombie/:id.
I wanted to add that if you're a beginner, the routes system can seem a little confusing...
The way to understand it is that Rails catches URL routes you send it. Rails is not magic, it's built on top of the HTTP protocol, and as such you can only send urls to your app, such as /zombies or /zombie/:id
Rails uses the ActiveDispatch middleware to take the URL's coming to the app, and routing them to a controller/action, where your code resides. Rails then takes the rendered HTML from your code and returns it to the browser, allowing the user to interact with their data etc.
The important thing to note is that Rails can only work with what you send it.
The Routes documents are a good step on seeing how this works; ultimately, you have to understand that you're responsible for sending the right URL's to rails, which can be achieved quite simply with the path helpers.
Objects
Finally, I also wanted to showcase something else - object orientation.
Rails is built on Ruby, which makes both of them object orientated. This is a programming pattern made popular with video games.
The counter to object orientated programming is flow based programming, which puts the flow of an application at the center of design. Typical "native" apps are flow-based, whilst games are object orientated.
The technical difference between the two is that object orientated programs store a series of "objects" (variables) in memory, allowing the user to "interact" with them.
Flow based design may keep just as much data in memory, but will only let the user access it through certain flow interactions. The best example of this would be the likes of setup/installation applications.
Anyway, because Ruby is object orientated, Rails has been designed to be such as well. Once you understand this, everything becomes much simpler.
Instead of thinking of Rails as controllers/variables/data, you want to think in terms of objects -- which object am I interacting with?
This is why most of Rails is built in a certain way -- to make the manipulation of objects work much smoother:
#config/routes.rb
resources :zombies #-> provides routes to interact with Zombie objects
#app/controller/zombies_controller.rb
class ZombiesController < ApplicationController
def index
#zombies = Zombie.all #-> show ALL zombie objects
end
def show
#zombie = Zombie.find params[:id] #-> find a single zombie object
end
def new
#zombie = Zombie.new #-> new zombie object
end
def create
#zombie = Zombie.new zombie_params
#zombie.save #-> save the new Zombie object
end
private
def zombie_params
params.require(:zombie).permit(:zombie, :params)
end
end
#app/models/zombie.rb
class Zombie < ActiveRecord::Base
has_many :enemies #-> each zombie object has many enemies
end
See how it all fits together?
If you focus on the object rather than the flow, it becomes much simpler.
I've been researching this topic for a day now, and I haven't seen a solution that could adequately allow this. I would have even give up and said that it's not possible, but I see large companies achieving this in their apps!
I need to know if the current user is following another user. I need to know this many times (for the current_user) without polling the DB again
The solution needs to be friendly for reuse. A solution (that's not friendly for reuse) I had come up with is as follows:
module UsersHelper
def is_following?(user)
return false if current_user == user
user.is_following = Relationship.find_by(followed_id: user.id, follower_id: current_user.id)
end
end
is_following?(#user) can now be called in any controller
Notice that I'm able to access current_user because this helper method will be called in a controller, not a model.
This implementation is cool for one model, maybe two... except I need to do this in almost every many-many relationship I have in the app. So it has to be scalable.
I'm referencing exactly what Twitter does with their following.
I'm implementing an activity feed for a client similar to Twitter's (it's only activity that pertains to the current signed in user -- i.e. who favorite his/her post, mentions, etc..).. It won't rely on 'push' but instead, the user will have to refresh the page in order to see new activity (for now).
I've been googling & searching around SO for the past hour to find the best way to implement this, and observers keep coming up in the solutions. I also notice that many of these are using push notification. I noticed the approach R Bates took in his public activity railscast btw, which is why I'm asking this question.
What if I don't want to use push notification, would callbacks be ok or even better? Do you think I would still need to use implement other things outside rails for scalability? (like how you may use "Pushapp" for push notifications)
Any suggestions on better solutions or light shed would be helpful.
This is for #gg_s
I'm assuming in this case you're saying I have an activity_feed table (receiver_id, sender_id, activity_type, & activity_id) (belongs to user, belongs_to activity_type (???), :polymorphic => true)
# application_controller.rb
def publish_to_user_feed(message)
current_user.activity_feed << message
end
# favorites_controller.rb
def create
# blah blah blah
publish_to_user_feed "This just happened."
end
In the the favorites_controller's 'create' action, "This just happened" could == "#favorite.user just favorited #favorite.post by #favorite.post.user"
Again, I hope I'm not being too pesky & am pretty sure what I'm asking is obvious, but I think this will help clear things up for me & also help future visitors.
Thanks again
For anyone that wants to know, I'm still working on this.. Just took a little break.. My main concern is how heavy it'll be on the db & other performance issues so if anyone wants to better this (using the code above), feel free :)
Solution: I don't want to overcomplicate things so I'm taking ap's advice.
Use neither.
Callbacks and observers are more complex in this case than you might think. The only automation they provide is the ability to be triggered upon model events. That's it. You are responsible to implement logic determining:
what just happened?
should it be reported?
what to report?
Extending this logic to support several types of activities is needlessly complex. Ditch the automation and publish activities from the controller as they happen on an as-needed basis.
Create a helper method to keep things DRY:
# application_controller.rb
def publish_to_user_feed(message)
current_user.activity_feed << message
end
Then manually post to a user's feed when and where necessary:
# some_controller.rb
def some_action
# perform some action
publish_to_user_feed "This just happened."
end
Reporting directly from the controller is clear, readable, DRY, maintainable, and adhere's to Rails' MVC pattern. No complex callback chains or observers to write.
As a bonus, it is trivial to perform activities without posting to activity feeds, e.g. administrative activity or user privacy settings.
This question has been repeatedly asked and answered with one-line assertions, such as "because it is an obvious violation of MVC." Frankly, I just don't get it. Indeed, it feels to me that putting session inside the controller merely an artifact that ApplicationController faces the network layer via a rack call, rather than an MVC mandate. Let me explain my problem.
Rolling an authentication from scratch, I found myself agonizing and spiking all over the place for lack of ability to articulate simple tests (session isn't available to the testing framework either). My authentication scheme, as almost all I have seen in rails, wanted to use the session hash as a persistence layer to retain the id for the User model of the "current user." Doesn't that feel FAR MORE like a model than a controller artifact?
The code smells are obvious whenever you look at the "typical" sessions controller (this one from Ryan Bates excellent screencasts). Desperate to shovel this concept in with rest, we see unhealthy language such as:
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
To me, this is a code smell, an obviously overlogicked controller that is screaming for refactoring! But we can't. Why? Oh yes, because it is a violation of MVC to put references to the session, used as a persistence lawyer into the model. WTF? Doesn't it say something to you that we seem to want to CALL THIS REST RESOURCE /sessions?
To see why this is just plain screwy, look at your login views -- hand-coded html, or use of the "_tags" API? If we had an ActiveModel model to do this code, then the create code could look like the usual scaffolding, or possibly even reduced to the "respond_with" one-liner.
def create
recognition = Recognition.new(params[:user])
if recognition.save
redirect_to root_url, :notice => "Thank you for signing up!"
else
render "new"
end
end
Then, take a look at the hand-coded html for all of those views! If Recognition was a model, persisted by session (or some other means that should not be the responsibility of the controller layer anyway), then you could simple use form builder or simple_form to generate the forms. Of course, we could simply pass the session hash to a "new_login" class method of Recognition, say Recognition.on(session).new(params[:recognition]), but that seems uglier than it has to be. Perhaps it is inherent, as we will want to use a current_user reference later in the application layer, perhaps Recognition.on(session).current_user similar to the way one might use a singleton pattern?
Just try to build your authentication package with strict BDD, and honestly tell me you haven't spiked this portion of it? If we had a Recognition model, this entire thing would be reduced to a simple set of unit tests without hackery. Now, instead we have the "sole" use case for integration testing, magic invasions of ActiveController modules and hacks to make speedy any acceptance testing of the logged_in_as predicate.
I think the entire point of ActiveModel was to facilitate this kind of rethinking and refactoring. Not all models use "the" database. Why not persist to the "session?"
I have been using devise and its kin for too long, burying these smells with the "dont mess with the gem" excuse I don't have to look at them. No longer! I think I am going to reject the zealots from now on. Sorry, to me, session is a persistence layer that should be manipulated in the Model layer of MVC. I posit, but am not sure, that the reason it lives in controllerland has more to do with the ugly or elegant fact that controllers are rack objects than any theoretical MVC magic.
So once again, is there a more elegant way to access the session layer than to have logic in the controller for that?
Maybe it's just me, but I don't sniff the code smell in that controller. I guess it depends on what you think should go into controllers versus models.
I think people take the "skinny controllers" idea to an unhealthy extreme at times. Yes, you want everything that can be in a model to be on the model: but controllers exist to change models based on application state, and you should allow them to fulfill that design goal. Having every controller be something like:
def create
Creator.create(params) # Pass all params to the creator model, which will automatically detect what object you're trying to create, make it, and then render the appropriate view
end
def show
Shower.show(params) # Find the model object and the view based on the params and render them together
end
Defies the idea of separation of concerns and creates nightmarish problems for people trying to update and maintain your code. Controllers should call model objects and even methods on those models to create and persist application state, and models should be agnostic to the state of the application. If you couple them together too tightly you'll end up with view and controller code in your models, at which point it becomes very difficult to determine what's where in your application.
If this is what you want and you think it serves your goals, then go for it -- but your code will be more difficult to maintain and will be hard for other people to understand. MVC exists because it's a sensible way to separate concerns and makes your code less surprising for other people.
All of this said, the session model you're proposing is actually a pretty good idea... and hence, it already exists. ;) The authlogic framework has a Sessions model: when logging in using authlogic, you create a new UserSession with the params object. UserSessions live in the models folder and exist solely to abstract away the nitty gritty of authentication into a controller-aware model class, so if that's what you're looking for, it's already been done for you! Go check out the authlogic github repository for documentation and usage examples.
I would still shy away from passing any kind of controller state into a real ActiveRecord model though. Allow your controllers to manipulate models and render the results of that manipulation as HTML -- it's what they're there for!