Does self as identifier can represent the resource on restful api? - ruby-on-rails

I'm working on REST API, and I trying to understand whether this looks legit in terms of REST.
I've players which using some mobile app with a login mechanism,
So the question is, if the player needs to update some attribute on his resource,
How the URL & PARAMS should looks like:
Option #1:
PUT /api/players/59/
PARAMS { some_attribute: "some_value" }
Option #2:
PUT /api/players/self
PARAMS { some_attribute: "some_value" }
The thing is, that the player doing the call with authentication, so it looks odd that he needs to send his id, it feels like he can send update on some other id, so when he sends 'self' it looks more suitable but uglier.
What's the REST point of view here?
Or maybe another option?

You should use first option
But in first option, request additional parameter like authentication_token
to verify the user and user is updating his information only.
This gist will help you in authenticating user
https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
It shows 2 method for authenticating user while communicating with API

Related

How to get the myshopify_domain from a ShopifyApp::Authenticated controller without an API call

I am trying to reduce unnecessary calls to the Shopify API from a controller that inherits from ShopifyApp::AuthenticatedController, for example to get the myshopify_domain:
myshopify_domain = ShopifyAPI::Shop.current.myshopify_domain
Is there some method in ShopifyApp::SessionRepository or somewhere else in the ShopifyApp that I can call to retrieve Shop.current.myshopify_domain without making an actual call to the Shopify API webservice? If not, can I store the myshopify_domain, once retrieved, in the ShopifyApp::SessionRepository?
If you are in the AuthenticatedController, dump the following to the console:
session.to_json
You will see that you can access all sorts of stuff about the current session, such as:
session["shopify_domain"]
session["shop_id"]
I had the same problem with a muli-store app, where I needed to pull data tied to a specific store. ShopifyAPI::Shop.current.myshopify_domain is redundant in that you are slowing down the controller waiting for Shopify's response, and you are tinking down the api bucket limit. The session object is the superior method to avoid all of that, and should be accessible from any controller which inherits the ShopifyApp::Authenticated controller.
Your question is confusing. At the point where you are doing calls to the API, you clearly already know the myshopify_domain, as you cannot do API calls with that the shops name and token.
So now we're past that point, and you are asking how you can somehow have the myshopify_domain be more convenient for you to use? Just make yourself a little helper so that when you open a session, you have access to shop_name or whatever you want.
Shopify always sends you shop name in their requests, so you're covered there as it's a param, and your own interface code and calls will also be setting up the shop name too, so you're really now into some pretty esoteric territory to need anything else.
Seem like you're caught in a classic "the dog chasing its own tail", but why?
The myshopify_domain is usually available in the session parameters:
if !session[:myshopify_domain].nil? && !session[:myshopify_domain].empty?
session[:myshopify_domain]
else
session[:myshopify_domain] = ShopifyAPI::Shop.current.myshopify_domain
end

Rails POST parameter with identical route parameter

I have the following route:
post ':client_name/login' => 'auth#log_in'
Client side I have a form with the following fields:
username
password
and client_name
I was hoping that the form data would override the route data so that I could change the client name if needed. But it doesn't. Any chance there's a setting for accessing that? Or a way to directly access only the post data?
F.y.i.: I don't show that <input> tag unless the user puts in the wrong password first. In that case I provide it (already filled out with the value from the route). This is helpful mainly for internal users who might get a link from one of their clients, they can then log in with a special value for the client that gives them more universal access.
Alright, until someone gives me a better solution (and I don't believe there is no better solution) I'll change the name of the posted client name to :override_client_name.

Passing parameters through OmniAuth

I need to pass some parameters to callback action. Judging from the source code, OmniAuth should add query string to callback URL but strangely it does not. When I open
/auth/facebook?from=partner
...and get redirected to Facebook, return_url is just
/auth/facebook/callback
...without any parameters.
After struggling with all the above answers, I figured out what to do regarding Facebook, which by default does not display the params in request.env["omniauth.auth"].
So -- If you are using a query string for the callback, similar to something like this:
"/auth/facebook?website_id=#{#website.id}"
The only way to get that website_id param is by using request.env["omniauth.params"]. NOTE: MAKE SURE YOU USE omniauth.params and not omniauth.auth -- this one tripped me up for a while.
Then, to test this out, you can inspect it within your controller action (notice the RAISE line...):
def create
raise request.env["omniauth.params"].to_yaml
# the rest of your create action code...
end
You should see your parameter there. Great. Now, go back to your controller and remove that RAISE line. Then, you can access the param as follows in your controller action:
params = request.env["omniauth.params"]
website_id = params["website_id"]
NOTE: in params["website_id"] you need to use quotes and NOT a symbol.
I guess the cookie thing works but why do all that when you can use the state variable as documented here: https://github.com/mkdynamic/omniauth-facebook
This is how I used it:
when creating the url you can just add state in the Query String and it will be available in the callback url as well.
user_omniauth_authorize_path(:facebook, :display => 'page', :state=>'123') %>
now the callback url will be
http://localhost:3000/users/auth/facebook/callback?state=123&code=ReallyLongCode#_=_
Now in the callback handler you can process the state
You can use the :params options, as in
omniauth_authorize_path(:user, :facebook, var: 'value', var2: 'value2' )
and later in the callback you can access request.env['omniauth.params'] to get the hash! :)
(copied from this answer)
What you want to do is dynamically set your callback to include the partner name in the url (not the url parameters), on a per authentication transaction basis, depending on which partner was involved. This means setting the callback url dynamically, for each authentication request. See this blog post to get started. The callback url automatically drops the url parameters, as you've noticed, so doing this with parameters won't work.
So, if instead of trying to pass the partner name/id in as a parameter (which is dropped), you structured your routes so that the partner_id and OmniAuth provider were part of the callback url, then you'd have something like:
/auth/:omniauth_provider/callback/:partner_id
...where a valid callback would be something like
/auth/facebook/callback/123456
...then you would know that a given callback came in from facebook, with partner id 123456
OmniAuth already has a built-in way to know where the user was, it's called "origin" as documented here:
https://github.com/intridea/omniauth/wiki/Saving-User-Location
You know, I think I might be trying to solve this the hard way.
Cookies might be the answer. I think you can solve this by having your login action store a cookie, and then redirecting to the proper /auth/:provider path for authentication, and when the callback is triggered (in SessionsController#create), you just read the cookie back to know where to redirect them to.
So, right now, your "login with facebook" link (or whatever you have you in your app) probably goes to /auth/facebook. Instead if you created a custom action like
POST /partner_auth
...and called it with the url...
POST example.com/partner_auth?from=partner&provider=facebook
Then you might have a controller like:
class PartnerAuth < ApplicationController
def create
cookies[:from] = params[:from] # creates a cookie storing the "from" value
redirect_to "auth/#{params[:provider]"
end
end
Then in the SessionsController#create action, you would have...
def create
...
destination = cookies[:from]
cookies[:from].delete
redirect_to destination # or whatever the appropriate thing is for your
# app to do with the "from" information
end
I tried to build a demo app to accomplish what I'd outlined in the other answer, but you're right - it was too complicated to try to dynamically inject a custom callback into the OmniAuth code. There is a configuration option to override the default callback, but it doesn't appear to be easy to set it dynamically.
So, it dawned on me that cookies would be way simpler, user-specific, and since you theoretically only need to store this from information for a very short time (between when the user tries to authenticate, and when the callback is triggered), it's no big deal to create a cookie, and then delete it when the callback gets hit.
Use the 'state' Variable. Facebook allows the user to set a STATE variable.
Here is how I did it, I appended the AUTH URL with ?state=providername
http://localhost/users/auth/facebook?state=providername
This param is returned to me at Callback as params['providername']
I devised the solution from the original Omniauth Path Method
user_omniauth_authorize_path(:facebook, :display => 'page', :state=>'123') %>

Generating secret URLs for a resource in rails

I've created a task management app that consists of lists and tasks. Users can only view their own lists and tasks. I would like to add the ability for a user to share a list if they like. Here are the steps I would like to accomplish:
User clicks a link from /list/show to share the list
User receives a secret URL to share: myapp.com/lists/1/23534512345234523 or whatever.
Secret URL redirects to a view other than /lists/show. Something like /lists/1/23534512345234523 which would be routed to /lists/secret_show or whatev.
Only users who have that url can see the information on that page.
Hope that is making sense. I imagine I would have to update the list record with a unique token to list.token. Then I would some how have to recieve the incoming URL and through a new action
lists#secret_share
def secret_share
...
end
Where I filtered for the list record by list.token and routed to secret_share. Then perhaps in the view I could simply restrict the view by the presence of the token in the URL.
Thoughts?
Whatever "secret URL" you hand out should not redirect to the real URL or you're going to create all kinds of opportunities for information leakage. It should be a strictly alternate URL.
Using routing for this seems like a good idea instead of using a separate controller. In your route you might want to pass an additional parameter to indicate this is a "secret" URL, like :secret => true where the value in question is something that cannot be submitted by the user to fake things out. User parameters are always strings, for instance, so using true should be a safe alternative.
This special parameter might disable access checking on your controller so that the page can be viewed by people that don't normally have access. You could also show a different layout using the layout method in your controller.

Thoughts regarding model ids in rails routes and validation

I am new to RoR and started working on a typical 'has_many' association (ie. a user has many friends). I have everything working correctly, but I don't like having the ids exposed in the url. I find that I need to add extra validation in my controller to make sure the ids represent valid associations in case the user manually entered different ids.
Personally I would like to see the ids out of the url and passed via some other means but that is not always possible. Shallow nesting of resources will help reduce the number of ids I need to validate at least.
What is the RoR philosophy on this? I have not seen anything specific to this issue.
Thanks
the URL has parameters if it is a GET url.
Try using POST parameters, which means your url will no longer be cluttered. Note that a malicious user can still send a made-up POST request using curl.
My approach to this is implementing proper authorization. If the user requests information for an object he is not permitted to read, this should be handled by an authorization framework.
With CanCan or Declarative Authorization you can define rules that replace your "manual" (and error-prone) checks in controllers.
I like the IDs being in the URL. That is what REST is about. Getting information for specific Resources, which have to be identified with an ID.
You can use Friendly ID in order to replace the integer ID by a slug (e.g. users/tollbooth instead of users/42).
basically ror routes by default takes id as key to generate urls. If you are not fan of id based urls then you can always override urls by using to_param inside model.
def to_param
# make sure this field is always present & unique
username
end
then by default you will start seeing username instead of id inside urls
How to find object inside controller actions
User.find_by_username(params[:id])
If you dont want to do this manually make use of slug gems like friendly id

Resources