I have a problem with devise_invitable 1.4.0 and strong parameters when I add additional custom parameters and I really hope somebody can guide me in the right direction. I am able to send invitations, but when an invited user accepts an invitation and enters a desired username, maiden name, password and confirmed password, the following error is shown:
Processing by Users::InvitationsController#update as HTML
Unpermitted parameters: username, name
The user is created as expected, but the 'username' and 'name' columns in the database are empty.
I have tried all the suggestions I could find for related issues, but none of the worked. I have noticed that if I change the app/controllers/users/invitations_controller.rb file in any way (eg inserting a blank space on an empty line) without restarting the webserver (Thin) the problem disappears - but the problem reappears when the webserver is restarted.
The various relevant files look like this:
routes.rb:
Rails.application.routes.draw do
root to: 'visitors#index'
#Tell rails to use the Devise controllers that were generated with this command:
# > rails generate devise:controllers users
#Using these generated controllers allows us to overwrite anything in the deault controllers.
devise_for :users, :path_names => {:sign_in => 'login', :sign_out => 'logout'}, controllers: {confirmations: "users/confirmations", passwords: "users/passwords", registrations: "users/registrations", sessions: "users/sessions", unlocks: "users/unlocks", :invitations => 'users/invitations'}
resources :users
end
config/initializers/devise.rb
Devise.setup do |config|
...
...
config.scoped_views = true
config.authentication_keys = [ :username ]
...
...
end
app/controllers/users/invitations_controller.rb:
class Users::InvitationsController < Devise::InvitationsController
private
# this is called when creating invitation
# should return an instance of resource class
def invite_resource
## skip sending emails on invite
resource_class.invite!(invite_params, current_inviter) do |u|
u.tenant = current_inviter.tenant
u.role = :user
end
end
def after_invite_path_for(resource)
users_path
end
def resource_params
params.permit(user: [:name, :email,:invitation_token, :username])[:user]
end
end
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
around_filter :scope_current_tenant
before_filter :configure_permitted_parameters, if: :devise_controller?
if Rails.env.development?
# https://github.com/RailsApps/rails-devise-pundit/issues/10
include Pundit
# https://github.com/elabs/pundit#ensuring-policies-are-used
# after_action :verify_authorized, except: :index
# after_action :verify_policy_scoped, only: :index
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
end
#############################################################################
private
#############################################################################
if Rails.env.development?
def user_not_authorized
flash[:alert] = "Access denied." # TODO: make sure this isn't hard coded English.
redirect_to (request.referrer || root_path) # Send them back to them page they came from, or to the root page.
end
end
def current_tenant
#current_tenant ||= current_user.tenant unless current_user.nil?
end
helper_method :current_tenant
def scope_current_tenant(&block)
if current_tenant.nil?
scope_visitor_schema
yield
else
current_tenant.scope_schema("public", &block)
end
end
def scope_visitor_schema()
original_search_path = ActiveRecord::Base.connection.schema_search_path
ActiveRecord::Base.connection.schema_search_path = 'public'
ensure
ActiveRecord::Base.connection.schema_search_path = original_search_path
end
#############################################################################
protected
#############################################################################
def configure_permitted_parameters
# Only add some parameters
devise_parameter_sanitizer.for(:account_update).concat [:name, :email]
# Override accepted parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:name, :username, :password, :password_confirmation,
:invitation_token)
end
end
end
app/models/user.rb:
class User < ActiveRecord::Base
enum role: [:user, :admin]
after_initialize :create_tenant, :if => :new_record?
belongs_to :tenant
# has_many :invitations, :class_name => self.to_s, :as => :invited_by
scope :unconfirmed, -> { where(confirmed_at: nil) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
# validate :username, presence: true, uniqueness: true, format: { with: /[a-zA-Z0-9]{4,20}/ }
def displayed_username
username.nil? ? "N/A" : username
end
def displayed_name
name.nil? ? "N/A" : name.titleize
end
def create_tenant
#The create_tenant method will also be called when looking up a user,
#so the following ensures a tenant is only created if it does not already
#exist - and the user has not been invited and assigned to an existing tenant:
if self.tenant.nil?
#Set role to 'admin' if a tenant is about to be created:
self.role = :admin #if self.tenant.nil?
self.tenant = Tenant.new
end
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
end
I finally found a fix, which was to place the parameter sanitizer directly in users/invitations_controller.rb instead of the application_controller.rb.
class Users::InvitationsController < Devise::InvitationsController
before_filter :configure_permitted_parameters, if: :devise_controller?
private
def configure_permitted_parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:username, :name, :email, :password, :password_confirmation, :invitation_token)
end
end
end
Related
I am having trouble making a test pass for my posts controller.
What is the proper way to test my create action inside the Posts controller with a polymorphic association?
I'm using Rails 4
Here's my code
Models:
class Topic < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :posts, as: :postable
has_many :posts, dependent: :destroy
validates :name, presence: true, length: {maximum: 50}
validates :description, presence: true, length: {maximum: 80}
validates :user_id, presence: true
is_impressionable
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :topic
belongs_to :category
belongs_to :postable, polymorphic: true
has_many :posts, as: :postable
validates :comment, presence: true
validates :user_id, presence: true
validates :topic_id, presence: true
validates :category_id, presence: true
default_scope { order(created_at: :asc) }
end
Post controller
class PostsController < ApplicationController
before_action :auth_user
before_action :set_post, only: [:destroy]
before_action :correct_user, only: [:destroy]
before_action :find_postable, only: [:create, :new]
def new
#post = #postable.posts.build
end
def create
#post = #postable.posts.build(post_params)
set_topic_id
set_category_id
#post.user_id = current_user.id
if #post.save
redirect_to topic_path(#post.topic.id)
else
redirect_to request.referer, notice: "Post unsuccessful!"
end
end
def destroy
#post.destroy
flash[:success] = 'Post deleted'
redirect_to request.referer || root_url
end
private
def set_post
#post = Post.find(params[:id])
end
def correct_user
#post = current_user.posts.find_by(id: params[:id])
redirect_to root_url if #post.nil?
end
def find_topic
#topic = Topic.find(params[:topic_id])
end
def find_postable
#postable = Post.find_by_id(params[:post_id]) if params[:post_id]
#postable = Topic.find_by_id(params[:topic_id]) if
params[:topic_id]
end
def post_params
params.require(:post).permit(:comment)
end
end
Post controller test:
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
def setup
#topic = topics(:topicone)
#post = posts(:postone)
#posttwo = posts(:posttwo)
#category = categories(:categoryone)
#user = users(:user1)
end
test 'should create post when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, post: { user_id: #user.id, category_id:
#category.id, topic_id: #topic.id, comment: "First reply!",
postable_id: #post.id, postable_type: "Post" }
end
end
end
When I run the test above I get this error:
ERROR["test_should_create_post_when_logged_in", PostsControllerTest,
2016-12-04 14:23:25 -0500]
test_should_create_post_when_logged_in#PostsControllerTest
(1480879405.93s)
NoMethodError: NoMethodError: undefined method `posts' for nil:NilClass
app/controllers/posts_controller.rb:13:in `create'
test/controllers/posts_controller_test.rb:28:in `block (2
levels) in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:25:in `block in
<class:PostsControllerTest>'
app/controllers/posts_controller.rb:13:in `create'
test/controllers/posts_controller_test.rb:28:in `block (2 levels)
in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:25:in `block in
<class:PostsControllerTest>'
From my understanding, I believe it's telling me that it can't find whether the create action is posting to a post or to a topic.
The site works great in development and production. The problem is in this test.
How can I rewrite this test and make it so it recognizes to whom it's posting to?
I found a solution for this shortly after.
In this case, to successfully test if a post is created on a topic, make sure to provide the topic_id.
test 'should create post on a topic when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, topic_id: #topic, post: {
user_id: #user.id,
category_id: #category.id,
comment: 'First post on a topic!' }
end
end
Now to test if a post is created on another post, make sure to provide the post_id.
test 'should create post on another post when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, post_id: #post, post: {
user_id: #user.id,
category_id: #category.id,
comment: 'First post on a topic!' }
end
end
I have a users_controller.rb that includes:
before_action :set_user, only: [:show, :edit, :update, :destroy]
load_and_authorize_resource
def index
#users = User.all
end
def show
end
def new
#user = User.new
end
private
def set_user
#user = User.find(params[:id])
end
My config/routes includes:
devise_for :users
devise_scope :user do
authenticated :user do
root 'switchboard#show', as: :authenticated_root
end
unauthenticated do
root 'devise/sessions#new', as: :unauthenticated_root
end
end
resources :users
When I navigate to http://localhost:3000/users I get a list of the users as I expect. But when I run the RSpec test:
require 'rails_helper'
RSpec.describe UsersController, :type => :controller do
describe "GET show" do
it "returns http success" do
get :show
expect(response).to be_success
end
end
end
I get the error No route matches {:action=>"show", :controller=>"users"} on the RSpec line get :show.
I've found countless queries about No route matches {:action=>"show", each one with a different cause, but none of them seems to match mine. What do I do to fix this (other than not test the feature!)
Because you have no route that matches controller user action show
However, you do have a route that matches user/:id (it's the :id you're missing)
So you could do...
get :show, id: 1
I created a starter app from RailsApps with the rails-devise-pundit example app. I am trying to write a user controller test because I plan to change some functionality and I want to make sure things still work. The pundit UserPolicy is not returning the correct value which is based on a role enum in the User class. The UserPolicy.index? method seen below is returning false when called from the first test in UsersControllerTest. Sorry there is a lot of code and detail here. I hope everyone can follow it.
Here's the failing test in UsersControllersTest. The response is a :redirect instead of :success.
require "test_helper"
class UsersControllerTest < ActionController::TestCase
def setup
#admin = users(:admin)
#admin.role = :admin
end
test "should get index page when authenticated as an admin" do
sign_in #admin
get :index
assert_response :success
end
...
end
Here's my user controller class just showing the index method where my problem is. authorize #users should call the UserPolicy.index? method.
class UsersController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized, except: [:show]
def index
#users = User.all
authorize #users
end
...
end
My pundit user policy class. When I change the index? method so it returns true, the response in my UsersControllerTest is :success. So for some reason #user.admin? is not returning the correct value.
class UserPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
#user.admin?
end
...
end
What is even stranger is that I created a UserPolicyTest class and when I test calling index? from there, I get the correct response. This test works correctly:
require 'test_helper'
class UserPolicyTest < ActiveSupport::TestCase
def setup
#admin = users(:admin)
#admin.role = :admin
end
def test_index
policy = UserPolicy.new #admin, nil
assert policy.index?
end
end
Here is my User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
enum role: [:user, :vip, :admin]
after_initialize :set_default_role, :if => :new_record?
validates :name, presence: true
def set_default_role
self.role ||= :user
end
end
Here's my test fixture for an admin user:
admin:
email: admin#example.com
name: Mr Admin
role: admin
encrypted_password: $2a$10$PoBe1MvkoGJsjMVTEjKqgeBUp.xdfzWoiDjBzQhtLAj16NqIa2fOy
remember_created_at: nil
sign_in_count: 3
current_sign_in_at: 2014-01-02 08:31:23
last_sign_in_at: 2014-01-02 08:31:23
current_sign_in_ip: 127.0.0.1
last_sign_in_ip: 127.0.0.1
confirmation_token: nil
confirmed_at: 2014-01-02 08:31:23
confirmation_sent_at: 2014-01-02 08:30:59
created_at: 2014-01-02 08:30:59
updated_at: 2014-01-02 08:31:23
I found that setting the role in the fixture doesn't work. I'm guessing that's because of the after_initialize :set_default_role, :if => :new_record? line in my User model. If there's another reason or a better way to handle this, please let me know.
UPDATE: Maybe this is being caused by strong parameters. When I tried debugging my code with pry, I found that in the UsersControllerTest, after signing in, the admin user had a role of 2 which is correct. But when it got to User.Policy.index?, the role was 0. I may need to add the role field to the devise strong parameters. I saw something about how to do that a while back. It didn't look easy. If someone knows the answer before I get to it, please let me know.
After I changed the value of #admin.role in setup, I didn't save the user. After adding #admin.save to the setup method, the test passed.
Total Rails novice here, just coming from PHP.
I'm running a Rails4+Active Admin setup with (device/cancan (disregarding those now).
Implementing strong_parameters. Please note, the code is totally broken. I've tried it in a bunch of ways and this is as close as I've gotten. Hopefully it gives a view into what I'm trying to do.
Been following a tutorial on implementing an API key setup, and trying to modify it to be able to generate an APIKey for a user in the admin.
I've added a column to AdminUser to generate an APIKey
It is correctly linked to
#app/admin/admin_users.rb
ActiveAdmin.register AdminUser do
index do
column :email
column :current_sign_in_at
column :last_sign_in_at
column :sign_in_count
default_actions
column('API', :sortable => :id) { |resource| link_to "(Re)Generate Key", new_admin_admin_user_api_key_path(resource.id) }
end
end
Which gives me a (correct) link to
/admin/admin_users/:admin_user_id/api_keys/new(.:format)
# app/admin/api_key.rb
ActiveAdmin.register APIKey do
belongs_to :admin_user
controller do
def create_for admin_user
key = APIKey.new
key.assign_params_from_controller(admin_user)
APIKey.create(:admin_user_id => params[:admin_user_id]);
end
def permitted_params
params.permit create_for [:admin_user_id]
end
end
end
#app/models/api_key.rb
class APIKey < ActiveRecord::Base
before_create :generate_access_token
belongs_to :admin_user, :foreign_key => "id", :inverse_of => :api_key
attr_accessible :admin_user_id
def assign_params_from_controller(params)
#params = params
end
private
def generate_access_token
begin
self.admin_user_id = params.admin_user_id
self.access_token = SecureRandom.hex
end while self.class.exists?(admin_user_id: admin_user_id, acces_token: access_token)
end
end
This code gives me:
NameError in Admin::APIKeysController#new
undefined local variable or method `params' for #<APIKey:0x000000078d6470>
def generate_access_token
begin
self.admin_user_id = params.admin_user_id
self.access_token = SecureRandom.hex
end while self.class.exists?(admin_user_id: admin_user_id, acces_token: access_token)
end
UPDATE, ANSWER:
Problem solved.
Ended up being on the right track. But was straying off because of type conversion errors.
My admin_user_id ended up being a 'string' in the database. Nice copy-paste job there.
Wrote a migration first
class AlterTableAPIKeys < ActiveRecord::Migration
def up
execute "DELETE FROM `api_keys` WHERE 1"
change_column :api_keys, :access_token, :string, { null: false }
change_column :api_keys, :admin_user_id, :integer, { null: false }
add_column :api_keys, :active, :boolean, {null: false, default: true }
remove_column :api_keys, :role
add_index :api_keys, ["admin_user_id"], name: "index_api_keys_on_admin_user_id", unique: false
add_index :api_keys, ["access_token"], name: "index_api_keys_on_access_token", unique: true
end
end
I didn't pass the access_token to the create.
I ended up with this.
# app/admin/api_key.rb
ActiveAdmin.register APIKey do
belongs_to :admin_user
controller do
def new
key = APIKey.create(:admin_user_id => params[:admin_user_id])
{:access_token => key.access_token}
redirect_to admin_admin_users_path, :notice => "API Key #{key.access_token} created! "
end
def permitted_params
params.permit api_key: [:admin_user_id]
end
end
end
# app/models/api_key.rb
class APIKey < ActiveRecord::Base
attr_accessible :access_token, :expires_at, :admin_user_id, :active, :application
before_create :generate_access_token
before_create :set_expiration
belongs_to :admin_user
def expired?
DateTime.now >= self.expires_at
end
private
def generate_access_token
begin
self.access_token = SecureRandom.hex
end while self.class.exists?(access_token: access_token)
end
def set_expiration
self.expires_at = DateTime.now+30
end
end
Obviously this does not account for access, might solve that with roles (i.e. uberadmin can regenerate other admins API, admins only their own).
I have a user who has_many services:
class User < ActiveRecord::Base
has_many :services
accepts_nested_attributes_for :services, :reject_if => lambda { |s| s[:name].blank? }, :allow_destroy => true
end
Here is my controller actions (devise)
def new
build_resource({})
5.times { resource.services.build }
...
end
def create
build_resource(sign_up_params)
resource.services.build(sign_up_params[:services_attributes])
...
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:email, :password, :password_confirmation,
services_attributes: [:name])
end
end
When I submit my form here is the related params hash:
{...,
"services_attributes"=>{
"0"=>{"name"=>"Test"},
"1"=>{"name"=>""},
"2"=>{"name"=>""},
"3"=>{"name"=>""},
...}
Giving me the following error:
unknown attribute: 0
In that case I don't know how to save multiple objects with strong parameters. The idea I have is to do something like this:
in my create method:
resource.services.build(sign_up_params[:services_attributes][:id])
It saves objects but I am not feeling ok with this... Thanks for your explaination!
It should be only:
def create
build_resource(sign_up_params)
...
end