I have a Rails 5.2 app built as API only. I am using Postman to test the endpoints. Currently, I am wrapping all of my JSON POSTS with the root element in order to get it to pass the 'strong parameters'. According to the Rails docs, however, with wrap_parameters set to accept JSON, I shouldn't have to be doing this, however, my POSTs fail if I don't. What am I doing wrong?
# users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show, :update, :destroy]
.....
# POST /users
def create
#user = User.new(user_params)
if #user.save
render json: #user, status: :created, location: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
.....
private
.....
# Only allow a trusted parameter "white list" through.
def user_params
params.require(:user).permit(:email, :password, :is_admin, :is_agent)
end
end
# config/initializers/wrap_parameters.rb
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
If I wrap the JSON in a user it works just fine, however, according to the Rails docs, I shouldn't have to do that.
http://api.rubyonrails.org/v5.2.0/classes/ActionController/ParamsWrapper.html
I have tried adding wrap_parameters User and wrap_parameters :user and wrap_parameters format: [:json] directly to the users_controller.rb but that doesn't do anything.
What am I doing wrong?
I figured it out. I had to add wrap_parameters :user, include: %i[email password is_admin is_agent] to the Users controller like so:
# users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show, :update, :destroy]
wrap_parameters :user, include: %i[email password is_admin is_agent]
.....
# POST /users
def create
#user = User.new(user_params)
if #user.save
render json: #user, status: :created, location: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
.....
private
.....
# Only allow a trusted parameter "white list" through.
def user_params
params.require(:user).permit(:email, :password, :is_admin, :is_agent)
end
end
I still don't fully understand why, but, it works. The docs imply this should be happening automagically through the wrap parameters module.
This is because of the parameter whitelisting.
def user_params
params.require(:user).permit(:email, :password, :is_admin, :is_agent)
end
Here , user key is required. As per the security point of view, its a good feature in RoR to whitelist the parameters
If you write only params.permit(:email, :password, :is_admin, :is_agent) , then you same request will work but removing the user object is not recommended.
Related
This section of Pundit section says that we could control which attributes are authorized to be updated. But it fails in case of the use of active_model_seriallizers gem:
def post_params
# originally geneated by scaffold
#params.require(:post).permit(:title, :body, :user_id)
#To deserialize with active_model_serializers
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params,
only: [:title, :body, :user]
)
end
If I modify the PostsController update action as Pundit suggested:
def update
if #post.update(permitted_attributes(#post))
render jsonapi: #post
else
render jsonapi: #post.errors, status: :unprocessable_entity
end
end
it fails with error:
ActionController::ParameterMissing (param is missing or the value is empty: post):
app/controllers/posts_controller.rb:29:in `update'
I also create the PostPolicy as follows:
class PostPolicy < ApplicationPolicy
def permitted_attributes
if user.admin? || user.national?
[:title, :body]
else
[:body]
end
end
end
but it has no impact on the above error.
Any idea on how can we do that?
The solution I came to (thanks to #max for some tips and tricks) is as follows:
Add the following line to config/application.rb:
config.action_controller.action_on_unpermitted_parameters = :raise
Add the rescue_from either to the AplicationController or the one you are precisely interested:
class ApplicationController < ActionController::API
include ActionController::MimeResponds
include Pundit
rescue_from Pundit::NotAuthorizedError, ActionController::UnpermittedParameters, with: :user_not_authorized
...
private
def user_not_authorized
render jsonapi: errors_response, status: :unathorized
end
def errors_response
{
errors:
[
{ message: 'You are not authorized to perform this action.' }
]
}
end
end
Then add pundit_params_for method to the PostsController and change the update action (in my case I'd like to restrict some attributes in update action only:)
class PostsController < ApplicationController
...
def update
if #post.update(permitted_attributes(#post))
render jsonapi: #post
else
render jsonapi: #post.errors, status: :unprocessable_entity
end
end
private
def post_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params,
only: [:title, :body, :user]
)
end
def pundit_params_for(_record)
params.fetch(:data, {}).fetch(:attributes, {})
end
end
VoilĂ . Now if an unpermitted attribute will be submitted for the update action, the response will have 500 status and contain the error as specified in ApplicationController#errors_response method.
ATTENTION: It still fails if you have some relations posted with the request (for example, you can have an Author as belongs_to relation with Post). Using pundit_params_for as before will fail to extract the corresponding author_id value. To see the way, here my another post where I explained how to use it.
Hope this helps.
I have an application that is using both Devise and Knock. It is using Devise to power the authentication for Active Admin and Knock gem is providing the authentication for my API's
The issue I have is that Knock can't seem to find current_user and I believe this is likely because I am using Devise in the same project.
I have the following setup:
Api Controller
class ApiController < ActionController::API
include Knock::Authenticable
end
User Controller (for API not ActiveAdmin)
module Api
module V1
class UsersController < ApiController
before_action :set_user, only: [:show, :update, :destroy]
# GET /users/1
def show
render json: #user
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
render json: #user.id, status: :created
else
render json: #user.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /users/1
def update
if #user.update(user_params)
render json: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
# DELETE /users/1
def destroy
#user.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
end
end
Auth Controller
module Api
module V1
class AuthController < ApiController
def auth
render json: { status: 200, user: current_user }
end
end
end
end
Current User in this Auth controller returns nothing however in another project I have, without devise, this will correctly return the user.
Is there a way to redefine what current_user is or assign it to something different for the purposes of using Knock?
Try this in your ApplicationController
# JWT: Knock defines it's own current_user method unless one is already
# defined. As controller class is cached between requests, this method
# stays and interferes with a browser-originated requests which rely on
# Devise's implementation of current_user. As we define the method here,
# Knock does not reimplement it anymore but we have to do its thing
# manually.
def current_user
if token
#_current_user ||= begin
Knock::AuthToken.new(token: token).entity_for(User)
rescue
nil
end
else
super
end
end
private
# JWT: No need to try and load session as there is none in an API request
def skip_session
request.session_options[:skip] = true if token
end
# JWT: overriding Knock's method to manually trigger Devise's auth.
# When there is no token we assume the request comes from the browser so
# has a session (potentially with warden key) attached.
def authenticate_entity(entity_name)
if token
super(entity_name)
else
current_user
end
end
I am making an api with ruby on rails, and I am trying to use token based authentication. Everything works just fine, but Rails is saying that the method authenticate_with_http_token is undefined.
This is the error it is giving:
{"status":500,"error":"Internal Server Error","exception":"#\u003cNoMethodError: undefined method `authenticate_with_http_token' for #\u003cUsersController:0x007fa8ac16dee0\u003e\u003e","traces":{"Application Trace":[{"id":0,"trace":"app/controllers/users_controller.rb:59:in `authenticate_token'"},{"id":1,"trace":"app/controllers/users_controller.rb:55:in `authenticate'"}],"Framework Trace":[{"id":2,"trace":"activesupport (5.0.0.beta3) lib/active_support/callbacks.rb:382:in `block in make_lambda'"}
This is the code for my controller:
class UsersController < ApplicationController
before_action :set_user, only: [:show, :update, :destroy]
before_action :authenticate, only: [:show, :update, :destroy]
# GET /users
def index
#users = User.all
render json: #users
end
# GET /users/1
def show
render json: #user
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
render json: #user, status: :created, location: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /users/1
def update
if #user.update(user_params)
render json: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
# DELETE /users/1
def destroy
#user.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :school_id, :auth_token, :password_digest)
end
def authenticate
authenticate_token || render_unauthorized
end
def authenticate_token
authenticate_with_http_token { |token, options| User.find_by(auth_token: token) }
end
end
Try to include that in your ApplicationController or UsersController
include ActionController::HttpAuthentication::Token::ControllerMethods
I guess you are trying to use the new Rails 5 API-only?
If so you probably inherited your ApplicationController from
class ApplicationController < ActionController::API
instead of
class ApplicationController < ActionController::Base
Please take note that ActionController::API is a downsized version of ActionController which does not include ALL modules. One of the ones left out is actually ActionController::HttpAuthentication::Token.
Including it in your ApplicationController (or a specialized controller if you just need it in one place) should fix it:
include ActionController::HttpAuthentication::Basic
This was working, but I've made a change somewhere and now cannot get it to work. I have a nested form to create a company along with the first contact that uses Devise. The company record is being created but the contact (devise model) isn't being created. If I create the company and then add a contact to the company, the contact is created so I'm pretty sure the controller works. Need another set of eyes to spot my mistake.
Error:
RuntimeError in CompaniesController#create
Could not find a valid mapping for nil
CompaniesController (relevant methods):
class CompaniesController < ApplicationController
before_action :set_company, only: [:show, :edit, :update, :destroy]
before_action :authenticate_contact!, only: [:index, :show, :edit, :update, :destroy]
# GET /companies/new
def new
#company = Company.new
#company.contacts.build
end
# POST /companies
# POST /companies.json
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
sign_in(#company.contacts.first)
format.html { redirect_to #company, notice: 'Company was successfully created.' }
format.json { render action: 'show', status: :created, location: #company }
else
format.html { render action: 'new' }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def company_params
params.require(:company).permit(:name, :legal_entity, :KVK_number, :VAT_number, :KVKdoc, contacts_attributes:[:first_name, :last_name, :address, :phone, :email, :postcode, :password] )
end
end
Contacts Registration Controller:
class Contacts::RegistrationsController < Devise::RegistrationsController
# This is a custom controller that inherits from the standard Devise Registration Controller.
# This controller is used as the user is added to the existing account not it's own account.
# Skips the standard Devise authentication filter to allow for an active user session to create a user recorod.
skip_before_filter :require_no_authentication
# Custom method to ensure that the user on the page has an active session to ensure that the account is accessable.
before_filter :has_current_contact
# Create method is just a call to the standard Devise create method
def create
super
end
protected
# Stop the user sessisons from being switched to the new user
def sign_up(resource_name, resource)
true
end
# Test to see that the person on the page has an active session
def has_current_contact
if current_contact.nil?
redirect_to root_path, alert: 'You need to be logged in to do this!'
end
end
# Sets the permited params for the registration. Inherits the standard devise parameters defined in the
# application controller and merges the account id of the current user.
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up).merge(company_id: current_contact.company_id)
end
end
I can see that the code throws the error on the attempt to find a contact for the company and sign in since it can't find a contact given that none has been created, but can't figure out why it's not being created.
Help?
I'm testing some gems since I'm about to create an API for a iOS app. This time I'm using Rails-API and Devise for user registrations. I followed this gist that I forked from a forked-forked gist, I did some changes and a User can register and login, but, when I use before_filter :authenticate_api_user! on a controller, after login the current_api_user is nil. According to this from the source code of divise, I a use devise_for inside a namespace then everything changes. This is my code.
Routes:
Todo::Application.routes.draw do
namespace :api do
devise_for :users
resources :users, only: [:index, :show]
resources :videos, only: [:create]
get "/users/show", to: "users#show"
end
end
app/api/sessions_controller.rb
class Api::SessionsController < ApplicationController
before_filter :authenticate_api_user!, except: [:create]
before_filter :ensure_user_login_param_exists, only: [:create]
# before_filter :ensure_email_param_exists, only: [:create]
# before_filter :ensure_password_param_exists, only: [:create]
respond_to :json
def create
resource = User.find_for_database_authentication(email: params[:user_login][:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user_login][:password])
sign_in(:api_user, resource)
#resource.ensure_authentication_token!
render json: { success: true, email: current_api_user.email }, status: :created
return
end
invalid_login_attempt
end
.
.
.
end
app/api/registrations_controller.rb
class Api::RegistrationsController < ApplicationController
respond_to :json
def create
user = User.new(user_params)
if user.save
render json: user.as_json(email: user.email), status: :created
return
else
warden.custom_failure!
render json: user.errors, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
I hope someone can help me. By the way I'm really accepting any advice about the best way to create an API using rails. It's my first time.
Thank you in advance.