pundit authorizations in a has_many relationship - ruby-on-rails

I'm having some trouble implementing one last step in my pundit authorizations...I have a projects model as well as a project_policy that authorizes which users in the app can view and interact w/ the project. However, my projects have several components - one of which is an index of project versions that is accessed within the project (a project has_many versions)...so I need to be able to restrict users access to this list of versions as well... along with the default project profile (I restrict if a users is not an owner/admin or collaborator). I currently have these 'versions', however, setup in their own model.
Details: Currently if I am not an admin/owner or collaborator, the restrictions work when I try to access the url projects/20 (it will redirect me and say "you're not authorized"), but if i navigate to 'projects/20/versions' or 'projects/20/versions/1' or 'projects/20/versions/new' it does not restrict me. Do I need to create a new version_policy for this or can i just restrict this within the project_policy since technically the versions are a part of the project? & what would the code look like that I need to add?
My code is as follows - thanks in advance for any and all help...
PROJECT_POLICY.RB
class Scope < Struct.new(:user, :scope)
end
def update?
user.project_admin? or user.project_collaborator?
end
# method used in projects controller, (study pundit docs)
def visit?
if project.is_private?
user.project_admin?(project) || user.project_owner?(project) || user.project_collaborator?(project)
else
true
end
end
def main_profile?
true
end
def projectversions?
user.project_admin?(project) || user.project_owner?(project)
end
def settings?
user.project_admin?(project) || user.project_owner?(project)
end
def collaboration?
user.project_admin?(project) || user.project_owner?(project)
end
def invite_admin?
user.project_admin?(project) || user.project_owner?(project)
end
def invite_collaborator?
user.project_admin?(project) || user.project_owner?(project)
end
end
PROJECT.RB (PROJECTS MODEL)
class Project < ActiveRecord::Base
...
...
has_many :versions, dependent: :destroy
validates :title, presence: true, length: { maximum: 100 }
validates :background, presence: true
validates :user_id, presence: true
default_scope -> { order('created_at DESC') }
def user_name
owner.try(:name)
end
def user_name=(name)
self.user = User.find_by_name(name) if name.present?
end
def private?
self.is_private == true
end
def public?
self.is_private == false
end
end
PROJECTS_CONTROLLER.RB
class ProjectsController < ApplicationController
before_filter :signed_in_user, only: [:create, :new, :edit, :update]
# Creates redirect and alert when pundit sees someone is not authorized (via :not_authorized_in_project below)
rescue_from Pundit::NotAuthorizedError, :with=>:not_authorized_in_project
def new
#project = Project.new
end
def show
#project = Project.find(params[:id])
authorize #project, :visit?
#user = User.where(:id => #project.user_id).first
rescue Pundit::NotAuthorizedError
flash[:danger] = "You are not authorized to access this page."
redirect_to projects_path || root_path
end
def create
#project = current_user.own_projects.build(project_params)
if #project.save
flash[:success] = "Welcome to your new project."
redirect_to #project
else
render 'new'
end
end
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
flash[:success] = "Project Created"
redirect_to #project
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Project destroyed"
redirect_to users_path
end
def projectadmins
#title = "Project Admins"
#project = Project.find(params[:id])
authorize #project, :visit?
#projects = #project.projectadmins.paginate(page: params[:page])
render 'show_projectadmin_project'
rescue Pundit::NotAuthorizedError
flash[:danger] = "You are not authorized to access this page."
redirect_to projects_path || root_path
end
def projectversions
*# If I should place the authorizations in this controller...What code should go here? And how would I also restrict the new/show actions for projects/versions i.e, 'projects/20/versions/new', etc?*
end
def settings
#project = Project.find(params[:project_id])
authorize #project
render 'settings'
end
private
def project_params
params.require(:project).permit(:title, :background, :is_private)
end
# USED FOR PUNDIT REDIRECT & ALERT FLASH WHEN SOMEONE IS NOT AUTHORIZED TO ACCESS SOMETHING
def not_authorized_in_project
flash[:danger] = "You are not authorized to access this page."
redirect_to project_path(#project) || root_path
end
end
ROUTES.RB
ProductionApp::Application.routes.draw do
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :projects do
resources :versions
match '/settings'=>'projects#settings', :via=>:get, :as=>:settings
match '/collaboration'=>'projects#collaboration', :via=>:get, :as=>:collaboration
match '/invite_admin'=>'projects#invite_admin', :via=>:patch, :as=>:invite_admin
match '/invite_collaborator'=>'projects#invite_collaborator', :via=>:patch, :as=>:invite_collaborator
get :autocomplete_user_name, :on=>:collection
end
resources :versions do
resources :users
end
resources :projects do
member do
get :projectadmins
end
end
resources :admin_relationships, only: [:create, :destroy]
resources :collaborator_relationships, only: [:create, :destroy]
# get "static_pages/home"
# get "static_pages/help"
# get "static_pages/about"
end
much appreciated...let me know if anything else I missed would be helpful to see.

Related

Updating a referenced entity through the create form of another

I have 2 models that are linked through a joint table:
class Dailyreport < ApplicationRecord
max_paginates_per 9
belongs_to :owner
has_many :dailyreport_issues
has_many :issues, through: :dailyreport_issues
accepts_nested_attributes_for :issues, allow_destroy: true
end
class Issue < ApplicationRecord
belongs_to :project
belongs_to :owner
has_many :dailyreport_issues
has_many :dailyreports, through: :dailyreport_issues
max_paginates_per 10
before_create { |issue| issue.jiraid = issue.jiraid.upcase }
validates :jiraid, uniqueness: true
validates :jiraid, :project, :owner, :time_forecast, :time_real, presence: true
validates :jiraid, format: { with: /\b[a-zA-Z]{2,6}-[1-9]\d{0,3}\b/, message: 'must follow this format ABCXYZ-9999' }
validates :time_real, numericality: { only_float: true }
validates :time_forecast, numericality: { only_float: true }
end
class DailyreportIssue < ApplicationRecord
belongs_to :dailyreport
belongs_to :issue
end
I use nested forms 'cocoon gem' to generate issues inside the create form of the dailyreport.
I successfully implemented that with these 2 controllers:
class DailyreportsController < ApplicationController
helper DailyreportsHelper
before_action :define_dailyreport, only: [:edit, :show, :update, :destroy]
def index
#dailyreports = Dailyreport.all.order(created_at: :desc).page params[:page]
end
def new
#dailyreport = Dailyreport.new
#dailyreport.issues.build
#issues = Issue.all.order(created_at: :desc)
end
def edit
end
def show
end
def owner_dailyreport
#owner_dailyreport = current_user.owner.dailyreports
end
def create
#dailyreport = Dailyreport.new(dailyreport_params)
#dailyreport.issues.each do |cr_issue|
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{cr_issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{cr_issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(cr_issue)
issue_time_real_from_jira(cr_issue)
end
if #dailyreport.save!
redirect_to #dailyreport, notice: 'Dailyreport was successfully created.'
else
render :new
end
end
end
def update
if #dailyreport.update(dailyreport_params)
redirect_to #dailyreport, notice: 'Dailyreport was successfully updated.'
else
render :edit
end
end
def destroy
if current_user.admin? || current_user.email == #dailyreport.owner.email
#dailyreport.destroy
else
admin_only_access
end
previous_page
end
private
def dailyreport_params
params.require(:dailyreport).permit(
:comment,
:owner_id,
issues_attributes: [
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status,
:_destroy
]
)
end
def define_dailyreport
#dailyreport = Dailyreport.find(params[:id])
end
end
class IssuesController < ApplicationController
require 'net/http'
require 'uri'
before_action :define_issue, only: [:show, :edit, :update, :destroy]
before_action :admin_only_access, only: [:destroy, :edit, :update]
def index
#issues = Issue.all.order(created_at: :desc).page params[:page]
end
def search
if params[:search].blank?
redirect_to issues_path and return
else
#parameter = params[:search].downcase
#results = Issue.all.where('lower(jiraid) LIKE :search', search: "%#{#parameter}%").page params[:page]
end
end
def new
#issue = Issue.new
end
def show
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(#issue)
yes_api_response
end
end
def create
#issue = Issue.new(issue_params)
# Check if issue exists on JIRA
unless call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
end
# Get issue details from JIRA
issue_details_from_jira(#issue)
issue_time_real_from_jira(#issue)
# Save the issue
if #issue.save
flash.notice = "Issue #{#issue.jiraid} created"
redirect_to issues_path and return
else
flash.alert = "There was a problem saving #{#issue.jiraid}, check if all the fields are filled on the JIRA issue"
end
end
def edit
end
def update
if #issue.update(issue_params)
redirect_to issues_path
else
render :edit, status: :unprocessable_entity
end
end
def destroy
if current_user.admin?
#issue.destroy
else
admin_only_access
end
previous_page
end
private
def issue_params
params.require(:issue).permit(
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status
)
end
def define_issue
#issue = Issue.find(params[:id])
#issue_owner = Owner.find_by(params[:current_user])
end
end
My routesRails.application.routes.draw do
get '/search', to: 'issues#search'
get '/home/jira', to: 'home#jira'
get '/dailyreports/owner_dailyreport/:id', to: 'dailyreports#owner_dailyreport', :as => 'my_crs'
resources :projects
resources :issues
resources :departements
resources :owners
resources :dailyreports
# Devise routes
devise_scope :user do
get 'users', to: 'devise/sessions#new'
end
devise_for :users
authenticated :user do
root to: 'home#index', as: :authenticated_root
end
root to: redirect('/users/sign_in')
end
I am trying to implement an update or create process:
Check if the JIRAID exists in my DB
If it doesn't just get the data and save the dailyreport.
If it does, I call the API and get its updated details then update it and save the dailyreport.
And here I found some issues with the code I tried.
First when I update the issue then try to save the dailyreport, it throws the validation error (Jiraid exists) because the dailyreport.save is trying to update the issue again.
I also tried this:
def create
#dailyreport = Dailyreport.new(dailyreport_params)
issues_attributes = params[:dailyreport][:issues_attributes]
p("///////////////////////////////////ISSUES_ATTRIBUTES#{issues_attributes}")
issues_attributes.each do |_, issue_attributes|
p("~~~~~~~~~~~~~~~~~~~~~~ISSUE_ATTRIBUTE#{issue_attributes}")
# Call the JIRA API and check for errors
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{issue_attributes["jiraid"]}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{issue_attributes["jiraid"]} exists and is available on JIRA"
return
end
# Update the issue attributes with details from the JIRA API
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
p("~~~~~~~~~~~~~~~~~~~~~~JIRA ID IN THE DB: #{issue.jiraid}")
# Check if the issue already exists in the database
issue = Issue.find_by(jiraid: issue_attributes["jiraid"])
if issue
issue_details_from_jira(issue)
issue_time_real_from_jira(issue)
# Update the existing issue
issue.update(
time_forecast: issue.time_forecast,
time_real: issue.time_real,
status: issue.status
)
else
# Build and save a new issue if it doesn't exist
#dailyreport.issues.build(issue_attributes)
end
end
I know I have an issue here:
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
I am going to have to create an object to pass to my methods. But i don't know how.
I couldn't update the issue from the dailyreport controller too, so I tried passing the update method (+ the id) inside the strong params of the dailyreport. That resulted in a ForbiddenAttributes error.
I actually need a lead of how to approach this, not a specific solution. I think that my approach is wrong.
thank you in advance

NoMethodError - Rails - Can't track down the problem

I am currently working on a blog app. The blog has users, stories, and favourites. 1 user can have many stories and many favourites, and one story can have many favourites (1 from each user) too.
When I try to save a new favourite on the front end (the create route), I receive the following error:
undefined method `favourites' for #<Class:0x00007f850e24e290>
Extracted source (around line #11):
9 def create
10 #story = Story.find(params[:story_id])
11 #favourite = Story.favourites.new
12 #favourite.user = #current_user
13 #favourite.save
14 redirect_to story_path(#story)
I feel like this should be defined, and is almost the same way that I am using create with other controllers. Based on what I've found so far, I suspect that it is because the favourites in Story.favourites.new is nested within the Story, but I haven't found a way to sort it out yet. I'm quite new to Rails, so apologies if the answer is obvious. Any guidance is appreciated! Thanks for your help!
Below are some of the pertinent files.
favourites_controller.rb
class FavouritesController < ApplicationController
before_action :logged_in, except: [:index, :show]
def show
end
def create
#story = Story.find(params[:story_id])
#favourite = Story.favourites.new
#favourite.user = #current_user
#favourite.save
redirect_to story_path(#story)
end
end
stories_controller.rb
class StoriesController < ApplicationController
before_action :logged_in, except: [:index, :show]
# Stories list page (also homepage)
def index
# Topic filtering
#topic = params[:topic]
#stories = if #topic.present?
Story.where(topic: #topic)
else
Story.all
end
end
def new
#story = Story.new
end
def create
#story = Story.new(form_params)
#story.user = #current_user
if #story.save
redirect_to root_path
else
render 'new'
end
end
def show
#story = Story.find(params[:id])
end
def destroy
#story = Story.find(params[:id])
#story.destroy if #story.user == #current_user
redirect_to root_path
end
def edit
#story = Story.find(params[:id])
redirect_to root_path if #story.user != #current_user
end
def update
#story = Story.find(params[:id])
if #story.user == #current_user
if #story.update(form_params)
redirect_to story_path(#story)
else
render 'edit'
end
else
redirect_to root_path
end
end
def form_params
params.require(:story).permit(:title, :topic, :body)
end
end
models/favourite.rb
class Favourite < ApplicationRecord
belongs_to :story
belongs_to :user
validates :story, uniqueness: { scope: :user }
end
models/story.rb
class Story < ApplicationRecord
has_many :comments
has_many :favourites
belongs_to :user
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
def to_param
id.to_s + '-' + title.parameterize
end
end
config/routes.rb
Rails.application.routes.draw do
resources :stories do
resources :comments
resource :favourite
end
resources :users
resource :session
root 'stories#index'
end
the issue is in this line:
11 #favourite = Story.favourites.new
it should be:
11 #favourite = #story.favourites.new
because the class Story itself doesn't have the favourites method but its instances do.

form_for nested route url generation error

I have two models tea and a review. I have nested a route to create reviews for a specific tea but when I submit the form I am getting a UrlGeneration Eror based on missing a required key. Below is the controller action and route and picture of the error. I am trying to have a user create a new review from /teas/1/reviews/new it is a nested form the issue being it does not save the create.
Review Model
class Review < ApplicationRecord
belongs_to :user
belongs_to :tea
validates :title, presence: true
validates :rating, numericality: {only_integer: true, greater_than_or_equal_to: 0, less_than: 11}
validates :tea, uniqueness: {scope: :user, message: "has already been reviewed by you" }
scope :order_by_rating, ->{left_joins(:reviews).group(:id).order('avg(rating) desc')}
end
class ReviewsController < ApplicationController
before_action :set_review, only:[:create, :show, :edit, :update, :destroy]
def new
if #tea = Tea.find_by_id(params[:tea_id])
#review = #tea.reviews.build
else
#review = Review.new
end
end
def create
#review = current_user.reviews.build(review_params)
if #review.valid?
#review.save
redirect_to new_review_path(#review)
else
render :new
end
end
def show
#review = Review.find_by_id(params[:id])
end
def index
if #tea = Tea.find_by_id(params[:tea_id])
#reviews = #tea.reviews
else
#reviews = Review.all
end
end
def edit
end
def update
#review.update(review_params)
redirect_to tea_reviews_path(current_user.id)
end
def destroy
#review.destroy
flash[:delete_review] = "Review Deleted!"
redirect_to reviews_path(#review)
end
private
def review_params
params.require(:review).permit(:tea_id, :content, :rating,:title)
end
def set_review
#review = Review.find_by_id(params[:id])
redirect_to reviews_path if !#review
end
end
Route
resources :reviews
resources :teas do
resources :reviews, only: [:new, :index]
end
In line 62 of Reviews_controller
redirect_to review_path if !#review
In this line you are trying to redirect to a review show page if review doesn't exist,
hence when review doesn't exist it redirects to show path without an id, that's why you are getting an error.
Think carefully where do you want to redirect, if a review does not exist.
Per your updated question,
Remove :create from the before_action in the first line of the controller,
also, in your create action change the redirect to
redirect_to reviews_path
after the #review.save.

ruby - run http://localhost:3000/admin/users but redirect_to http://localhost:3000/admin/login

I am new in Ruby on Rails.
I want to run http://localhost:3000/admin/users to see users index page.
But when I run this link, it guide me to http://localhost:3000/admin/login.
Is there something wrongs with my route setting?
Rails.application.routes.draw do
get 'users/new'
get 'users/show'
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: '/letter_opener'
end
root to: 'helps#top'
# admin login
get 'admin/login', to: 'admin/login#index', as: 'admin/login'
get 'admin/logout', to: 'admin/login#logout'
post 'admin/login/login'
get 'admin', to: 'admin/projects#index', as: 'admin_top'
namespace :admin do
resources :users, only: %i(index new create)
resources :projects do
resources :project_users
resources :project_comments
end
resources :images
resources :categories
resources :campanies
end
end
users_controller.rb
class Admin::UsersController < AdminController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
#users = User.all
end
def show
end
def new
#user = User.new
end
def edit
end
#Post /admin/projects
def create
#user = User.new(user_params)
if #user.save
flash[:notice] = 'User saved successfully'
redirect_to :back
else
flash[:alert] = #user.errors
binding.pry
render :new
end
end
def update
end
def destroy
end
private
def set_user
#user = User.find(params [:id])
end
def user_params
params.require(:user).permit(:campany_id, :name, :email, :password_digest, :profile, :prefecture_id, :address)
end
end
Thank you!
Your UsersControllers is under the admin namespace, that's to say you must be logged in order to access to this.
If you want to have access without validating the user is currently logged in, then you'll have to remove the constraint or verification to the controller or to make a new controller and index method which point to /admin/users but this time without the user verification.
That's:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
...
def index
#users = User.all
end
...
end
# config/routes.rb
get '/users', to: 'users#index'
'/users' or '/admin/users' as you want to do it, but if you use the last one then any person must infer that's a restricted resource .

Cannot access "Current user" in rails

I am using devise and devise_ldap for my rails authentication. I am trying to use the built in helper, current user to display the users email on the welcome page of my application.
This is the code that I have tried to use to:
<% if user_signed_in? %>
<div>Signed in as... <%= current_user.email %></div>
<% end %>
when I sign in to the application, I get the error;
undefined method `email' for nil:NilClass
Here is my routes.rb
Rails.application.routes.draw do
devise_for :users
resources :users
resources :systems do
member do
get :targets, :sources
end
root 'systems#index'
end
and my users controller:
class UsersController < ApplicationController
authorize_resource
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
def index
#users = User.all.order("display_name asc")
end
# GET /users/1
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /stories/1/edit
def edit
respond_to do |format|
format.html
format.js
end
end
# POST /stories
def create
#user = User.new(user_params)
respond_to do |format|
puts 'user controller'
if #user.save!
format.html { redirect_to user_path(#user), notice: 'User was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /stories/1
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_path(#user), notice: 'User was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /stories/1
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_path notice: 'User was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:display_name, :email, :username)
end
end
my users model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
before_create :rememberable_value
before_save :get_ldap_values
devise :ldap_authenticatable, :rememberable, :trackable, :validatable
def get_ldap_values
if self.username
self.email = Devise::LDAP::Adapter.get_ldap_param(self.username,"mail").first if Devise::LDAP::Adapter.get_ldap_param(self.username,"mail")
self.display_name = Devise::LDAP::Adapter.get_ldap_param(self.username,"displayName").first if Devise::LDAP::Adapter.get_ldap_param(self.username,"displayName")
end
end
# def role?(role)
# return !!self.roles.find_by_name(role.to_s.camelize)
# end
def email_required?
false
end
def email_changed?
false
end
def rememberable_value
self.remember_token ||= Devise.friendly_token
end
def name_to_display
if self.display_name
self.display_name
else
self.username
end
end
def password_required?
false
end
def password_match?
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
end
I am not sure what I am missing to be able to access the current users information after a successful sign in.
Update
Here is the new routes file:
Rails.application.routes.draw do
devise_scope :user do
get '/users/sign_out' => 'devise/sessions#destroy'
get "/users/sign_in" => "devise/sessions#new"
# delete "/logout" => "devise/sessions#destroy"
end
devise_for :users
authenticate(:user) do
resources :users
resources :reports
resources :change_logs, only: [:index, :show]
resources :systems do
member do
get :targets, :sources
end
resources :change_logs, module: :systems
resources :components do
resources :change_logs, module: :components
end
resources :c_relations
end
resources :integrations
get '/static_pages/home' # => 'static_pages#home', as: 'home'
root 'systems#index'
end
In routes.rb you should enclose the rows following
devise_for :users
in a block
authenticate(:user) do
resources :users
[...]
end
The problem was that I had overridden the devise mapping and current_user. I removed them and was able to access current user in my views.
Do you have before_action :authenticate_user! in your controller chain at all?
Current_user could be nil after signin is if you aren't asking devise to authenticate the action.

Resources