What is the best practice for excluding certain fields/data in a RESTful response if the user requesting it shouldn't be able to see all of the data?
Example:
Person has a First Name, Last Name, and Date of Birth.
Both authenticated and non-authenticated users can make RESTful requests to /people.xml to get a full list of people. However, only authenticated users should be able to view all of the information. Non-authenticated users should only have the First and Last Name fields returned (excluding the Date Of Birth data).
Should the Person controller check for authentication before building the response? If user is authenticated they get everything, else they only get a subset? Does that break any rules of REST where /people.xml can send two separate results?
No, that's fine. It's the same resource, but with a different representations based on the authentication information. You could also serve different versions depending on what the Accept header contained (you should use that one instead of file extensions like .xml, by the way), or you could serve different language versions, or you could render the page different if the logged in user has specific personalization options defined. It's all legal. Consider a website that has a login box. If you're logged in, the page will be different. That's the same thing, except it doesn't specifically affect the enclosed information per se. Controlling caching and so forth in these cases is exactly what Cache-Control, Vary and friends are for. Also see http://www.subbu.org/blog/2007/12/vary-header-for-restful-applications
The same URL can yield different representations, depending on the request-headers. For example, Accept is commonly used to control the format of the response (f.ex. XML or JSON). Likewise, authentication-headers could be used to control how much is returned for an entity.
You can use the :only option of the to_xml method to restrict the fields returned by the controller, i.e.:
def show
#person = Person.find(params[:id])
payload = current_user.nil? ? #person.to_xml(:only =>
[:first_name, :last_name, :dob]) : #person
respond_to do |format|
format.xml { render :xml => payload }
end
end
I use serialize_with_options plugin, as most of the view data access configuration can be done at the model level.
class Person
serialize_with_options(:anonymous) do
only :first_name, :last_name, :dob
end
end
class PersonController
def show
#person = Person.find(:params[:id])
respond_to do |format|
format.xml { render :xml => current_user.nil? ? #person.to_xml(:anonymous):
#person }
end
end
end
Reference
1 serialize_with_options
Consider that different representations of the same data (depending on the user) could have surprising consequences if you have a cache somewhere in the middle. If the fully authenticated user makes the request first, then followed by the 'lesser' user, they might see surprising result: The same view as the fully authenticated user before. That's because there is no difference in the two requests as far as the cache is concerned (unless you make it aware of these issues). So, tread carefully.
I would also suggest separate namespaces, as proposed by Robert.
By the book, REST says that one resource should return the same result every request.
I would create a namespace with "unauthenticated" or something and make
/people.xml for authenticated
and
/unauthenticated/people.xml
They could be the same controller building different xml results for each request.
Related
I am trying to forward an action to another controller through the service object methodology in rails 5.2.
The create action should pass the user id for the create action but I am failing at passing that param appropriately.
Business logic is the following: a user rents out an asset, the potential renter makes a request, when the asset owner agrees to a visit, the rent user is created as a client in another controller to organise a visit.
I am trying to address the create action in the client controller as follows:
In the rent controller :
private
def visit(room, rent)
#newclient = NewclientService.create(params)
if #newclient.save
rent.Approved!
...
else
rent.Declined!
...
end
and then in the app/service/newclient_service.rb
module NewclientService
class << self
def create(params)
#rent = Rent.find_by(id: params[:id])
user = #rent.user_id
name = #rent.user.fullname
email = #rent.user.email
Client.create(user_id: user, name: name, email: email)
end
end
end
This code does the job. The db is filled up, validations and strong params seem to work and it seems to me robust/secure enough.
Question: is the service object (my way ?) route the most preferred way for forwarding that action ?
Thanks for your help,
I like the pattern in principle and it has really cleaned up the apps which I produce. There are a couple of nice gems that I typically use to get the job done and keep the controllers clean.
I use the mutations gem and simple_command. These two together give you a nice (almost completely) consistent API. The mutations gem in particular is what I use for digesting and resolving JSON input data from params which can then handle processes for me.
This is a good pattern in the sense that it encapsulates the logic of discrete functionality very well. For example, if you have a RegisterUser mutation, you can use that in a controller or you can use it to digest a whole list of objects etc. You can even use the builder option for attributes to process deeply nested json.
I would recommend checking it out.
https://github.com/cypriss/mutations
For those times where I am not processing JSON from an API and want to create discrete encapsulated functionality I generally use simple_command https://github.com/nebulab/simple_command. This approach is also great because it allows you to use the same components from any context. For example, a command called GetLatestTweets.call() could be used equally well from a controller as it could from the REPL.
Both of these libraries provide you with a result object which you can then process as appropriate
def create
outcome = NewClientMutation.run(params.require(:resource).permit!)
if outcome.success?
render json: outcome.result, status: :created
else
render json: {resource: outcome.result, errors: outcome.errors}, status: :unprocessable_entity
end
end
In my particular case I use 'permit!' since the mutations library ignores any parameters that aren't explicitly specified which means that strong parameters aren't necessary if you use this library as it filters parameters as well.
I've got a number of security concerns about my current application and wondering if I am leaving myself open to abuse, in the following arenas.
a) .My main access control method is by maining a current_user, current_company current_project method in my application controller. These methods return object based on stored session keys established when a user logs in and cleared when they log out. I.e if I want to know something about the current user, I can call "current_user.role" or if I want see whether the account a user is trying to change belongs to him, I check whether the associated account id which is requested in the url actually belongs to that user, essentially as follows
in Account controller
def account_info
redirect_to login_path if !user.logged_in
account_id=params[:account_id]
#account = Account.find(account_id)
unless account_belongs_to_user(account_id)
redirect_to unauthorized_path
end
end
In my application controller, when a user is initially authenticated, I do something like this:
session[:current_user_id] = user.id
and clear that session key when the user logs out.
Then when account is requested, and account_belongs_to_user is called, the application controller processes it, more or less like this:
def account_belongs_to_user(account_id)
account = Account.find(account_id)
return account.user_id==session[:current_user_id]
end
So I guess my security scheme ultimately relies on whether the session data is secure and not trivially spoofable.
b) When I render pages I sometimes pass objects which have senstive data to my erb pages to generate the page text.
For example, I might pass a "company" object (ActiveRecord) to the view to generate an invoice screen. But the company object, passed as #company, has a lot of sensitive data like access keys and the like. Not really being fully aware of the the internals, if I don't specifically include something like:
<%= #company.access_token %>
on my web page, can I be confident that the attributes of #company won't somehow be passed into the browser unless I specifically ask for them to be rendered on the page?
This is obviously an issue when using rails to serve data for say, AngularJS single page applications, as everything I pass for Angular to render the page I assume is probably accessible to an evil-doer even if not on the page itself, but I'm hoping that's not the case with pages generated server side by rails.
This may be a naive question, but thanks as I just want to be certain what I am doing before start spilling secrets all over the place.
put an authentication for the token using active_record callback
https://guides.rubyonrails.org/active_record_callbacks.html
Disclaimer: I'm doing something which may qualify for Code Smell of 2015 Award. Using rails 4.2, no javascript or anything like that.
I have a form into which users input their data. With this data I call a third-party API which will remain nameless. :)
I have no model, I'm not persisting anything. (Part of a larger app, not a one-pager.) Thus when faced with presenting the user with the response, I find myself stuck on how to render the data properly into a view. The response contains an array of hashes which I obviously intend to present the user.
I render the form into widgets/new, etc, create and process the request, etc, but then what?
I thought maybe I could make use of decorators to do my dirty work but not sure how to actually get the user off to the respective view. I don't care which view. Call it a widget_path.
WidgetsController < ApplicationController
def new
render :new
end
def create
# preparing request
...
# data = response, each_serializer, WidgetSerializer, root: false
# data = WidgetDecorator.new(render_serialized(response, WidgetSerializer))
# #data = WidgetDecorator.new(JSON.parse(response))
# redirect_to ??_path ... and take your #data with you
end
end
What do I do?
Your idea of Model is unfortunately corrupted by Rails itself (sorry).
A model is business logic not an ActiveRecord::Base (not necessarily). Controller methods shouldn't be big, ~5 lines long is probably the maximum with a ~100 lines max per controller file. Try to stick with this and it will automatically correct good chunck of code smells.
Anyway, you may handle this with a Model, as a PORO (plain old ruby object).
class MyApiResponse
attr_reader :myapikey
attr_reader :whatever
def initialize(myapikey, whatever)
#myapikey = myapikey
#whatever = whatever
end
def get
#_response ||= JSON.parse(run_api_stuff(myapikey))
end
end
So in controller you would do something like
def create
myapiresponse = MyApiResonse.new(myapikey, whatever)
#response = myapiresponse.get
end
Last but not least, you can't pass what you obtained through the API in the redirect. You are subject to HTTP limits so, you have a limit on GET params size, a limit on session and you can't redirect to a POST. You have 3 options
Best is store last api request for given user in the database and fetch it back through an ID (which will travel through the redirect)
Store it in session if request is really small (and you must ensure it is small!)
Perform the API request again after the redirect, horrible. Otherwise perform the API request only after redirect, not sure if this is an option though
I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.
As a long-time Ruby and Rails user, it never struck me until today to really think about the get-and-redirect pattern in Rails. The typical example of this would be calling a create() action, and then redirecting the user to a show() action to display the newly-created item:
class JournalEntries
def index
#entries = JournalEntry.all
end
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
redirect_to :action => "index"
end
end
However, this has the inherent disadvantage that you are doubling your network traffic. This both slows down your users' site experience, as well as increasing your bandwidth charges.
So why not just do this instead:
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
index
Same output, and no extra overhead required. But in addition to this, there is an even more substantial problem: redirect_to can only redirect using GET. This causes major problems for RESTful apps that use four different HTTP methods.
In my case, I wanted a user to be able to call /journals/8 and retrieve the Journal with that ID. If it wasn't found, I wanted to create a new, empty Journal object. In either case, the Journal object would then be sent to the caller.
Note that the create() method in RESTful Rails is routed from "POST /players". But since redirect_to (and the underlying HTTP redirect) can only send GET requests, it actually redirects to "GET /players", which is the index() method. This behavior is clearly wrong.
The only solution I could think of was to simply call create() instead of redirect_to() as in my example above. It seems to work fine.
Any thoughts on why redirect_to is preferred to calling actions directly?
If they do a page refresh they don't get that annoying "Resend data?" popup
It's not just that the popup is annoying (and makes no sense to most users) -- if the user clicks "yes, re-do the POST", he'll end up creating another Journal Entry (or whatever).
Also, it's annoying for the URL to read /posts/create instead of /posts since the user cannot copy / re-use it.
The reason for it is as you point out. You redirect to a GET request, which is correct when it comes to REST (only do updates with POST/PUT, only get data with GET).
A redirect surely gives a little overhead with the redirect, but since no data is actually being sent between the browser and the server except for the POST data and the redirect (which is only sending the new url to the browser) I don't think that the issue of bandwith is of concern.
But on another point, you should not redirect to /journals (by calling redirect_to :index), you should redirect it to the newly created journal entry (by calling redirect_to #entry) which will work if you set up the routes correctly by, for instance map.resources :journals
Update:
I think, for creating the Journal when one doesn't exist, you should ask the user for more input. What is the reason for you to create the entry? The entry should have some text or some other input from the user, so I think from a REST (and rails) perspective you should actually redirect it to the new() method (with a GET request) where the user can input the additional information, that one will then POST the input and create the entry and after redirect to the newly created entry.
If you don't have any extra information that needs to put in, I'm not sure how to do it in a RESTful way, but I would probably have done it by putting the creation logic in a separate method that I would call from the create() and the show() method and then just continue with the show(), not redirecting at all, but also not calling the resource method.
I'm a Python/Django person, but the reasons for the redirect is language agnostic:
If they do a page refresh they don't get that annoying "Resend data?" popup.
This gives you a completely clean, RESTful URL for the page they are looking at. If you used POST it might not matter that much, but if GET was used for the update then you definitely want to get rid of any dangling params.