I need to create some access validations in my application.
And I need your help for that.
How do I get the standard user to access only their own records and not be able to change another user?
I have several tables, and everyone has user_id, profile_id, config_id, post_id, dash_id ....
All are filled, but I don't know how to compare the user_id of the user table with the table config_id, profile_id, post_id, dash_id
How do I do that?
I currently use
https://github.com/danschultzer/pow
I ended up opening a post on the elixir forum, but without success.
https://elixirforum.com/t/improving-access-permissions-pow-authentication/32486
Could help me create this plug?
Currently the only thing I have is
EnsureRolePlug
defmodule MyprojectWeb.EnsureRolePlug do
#moduledoc """
This plug ensures that a user has a particular role.
## Example
plug MyprojectWeb.EnsureRolePlug, [:user, :admin]
plug MyprojectWeb.EnsureRolePlug, :admin
plug MyprojectWeb.EnsureRolePlug, ~w(user admin)a
"""
import Plug.Conn, only: [halt: 1]
alias MyprojectWeb.Router.Helpers, as: Routes
alias Phoenix.Controller
alias Plug.Conn
alias Pow.Plug
#doc false
#spec init(any()) :: any()
def init(config), do: config
#doc false
#spec call(Conn.t(), atom() | binary() | [atom()] | [binary()]) :: Conn.t()
def call(conn, roles) do
conn
|> Plug.current_user()
|> has_role?(roles)
|> maybe_halt(conn)
end
defp has_role?(nil, _roles), do: false
defp has_role?(user, roles) when is_list(roles), do: Enum.any?(roles, &has_role?(user, &1))
defp has_role?(user, role) when is_atom(role), do: has_role?(user, Atom.to_string(role))
defp has_role?(%{role: role}, role), do: true
defp has_role?(_user, _role), do: false
defp maybe_halt(true, conn), do: conn
defp maybe_halt(_any, conn) do
conn
|> Controller.put_flash(:error, "Acesso não autorizado!")
|> Controller.redirect(to: Routes.dashboard_path(conn, :index))
|> halt()
end
end
Related
When using Rails with ActiveRecord (and PostgreSQL), executing "simple" queries adds a name to them, e.g. calling
Article.all
# => Article Load (2.6ms) SELECT "articles".* FROM "articles"
names the query Article Load. However, when executing slightly more complex queries, no name is being generated, as for example with
Article.group(:article_group_id).count
# => (1.2ms) SELECT COUNT(*) AS count_all, "articles"."article_group_id" AS articles_article_group_id FROM "articles" GROUP BY "articles"."article_group_id"
I can add a name if executing a custom query using the execute method:
ActiveRecord::Base.connection.execute("SELECT * FROM articles", "My custom query name")
# => My custom query name (2.5ms) SELECT * FROM articles
But is there a way to add a custom name to a query built with the ActiveRecord-methods?
If you wonder why: The name is useful for all kinds of monitoring, e.g. when looking at slow queries in AppSignal.
Since you just want to custom query name for monitoring purpose, so i think you only need to change the query name in the ActiveRecord::ConnectionAdapters#log method, this method is the one log the sql query that be executed, include the query name.
Here is my solution:
# lib/active_record/base.rb
# note that MUST be base.rb
# otherwise you need to add initializer to extend Rails core
#
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
attr_accessor :log_tag
private
alias old_log log
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil)
if name != 'SCHEMA'
name = #log_tag
#log_tag = nil # reset
end
old_log(sql, name, binds, type_casted_binds, statement_name) do
yield
end
end
end
end
module QueryMethods
def log_tag(tag_name) # class method
spawn.log_tag(tag_name)
self
end
end
module Querying
delegate :log_tag, to: :all
end
class Relation
def log_tag(tag_name) # instance method
conn = klass.connection
conn.log_tag = tag_name
self
end
end
end
Demo
Task.log_tag("DEMO").group(:status).count
# DEMO (0.7ms) SELECT COUNT(*) AS count_all, "tasks"."status" AS tasks_status FROM "tasks" GROUP BY "tasks"."status"
Task.where(status: 6).log_tag("SIX").first(20)
# SIX (0.8ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."status" = ? ORDER BY "tasks"."id" ASC LIMIT ?
Task.where(status: 6).first(20)
# (0.8ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."status" = ? ORDER BY "tasks"."id" ASC LIMIT ?
Note
In case you want to fix query name for specific query, you can use a hash with key is the whole the specific sql string (or hash of whole sql, such as the way Rails core cache query: query_signature = ActiveSupport::Digest.hexdigest(to_sql)) and the value is the query name you want.
# set up before hand
ActiveRecord::ConnectionAdapters::LogTags[Product.where...to_sql] = "DEMO"
# AbstractAdapter
LogTags = Hash.new
def log(sql, name...)
name = LogTags[sql]
# ...
end
I've got the following test in Phoenix:
test "list_orders/0 returns all orders" do
{:ok, user} = %User{}
|> User.changeset(#user_attrs)
|> Repo.insert()
changeset = %Order{}
|> Order.changeset(#valid_attrs)
|> Ecto.Changeset.put_change(:user_id, user.id)
{:ok, order} = Repo.insert(changeset)
assert MWS.list_orders() == [order]
end
Which is failing to insert changeset into the Repo because user_id is failing a foreign key constraint. But I'm using the user.id that's returned from the user successfully being inserted into the database?
Any idea what I'm doing wrong here?
Thanks!
As Kalvin Hom points out, you need to look at the schema/migrations to understand this:
defmodule BarronWatchCompany.Repo.Migrations.AddUsersToOrders do
use Ecto.Migration
def change do
alter table(:orders) do
add :user_id, references(:orders, on_delete: :nothing)
end
create index(:orders, [:user_id])
end
end
The problem is that I was referenceing orders and not users. I've migrated the database:
defmodule BarronWatchCompany.Repo.Migrations.AddUserToOrders do
use Ecto.Migration
def up do
drop constraint(:orders, "orders_user_id_fkey")
alter table(:orders) do
modify :user_id, references(:users, on_delete: :nothing), null: false
end
end
end
And I can now create orders :)
I have "influencer_platforms" table in my DB which consists of:
id | influencer_handle | name
I want to query them and group by name like:
%{
name1: [%{id: 1, influencer_handle: "i1"}, %{id: 3, influencer_handle: "i2"}],
name2: [%{id: 2, influencer_handle: "i3"}]
}
How do do that in Ecto? So far I have:
defmacrop influencer_platform_json(influencer) do
quote do
fragment(
"jsonb_agg(?)",
fragment(
"json_build_object(?, ?, ?, ?)",
"id", unquote(influencer).id,
"influencer_handle", unquote(influencer).influencer_handle
)
)
end
end
def all do
from ip in InfluencerPlatform,
group_by: :name,
select: %{
ip.name => influencer_platform_json(ip)
}
end
Is is a more elegant way to achieve it?
If you want all this to be done in the database, you could collapse the two fragments into one. You also don't need to pass the column names as arguments to fragment if they're simple constants; you can put the column names inside the query in fragment.
defmacrop influencer_platform_json(influencer) do
quote do
fragment("jsonb_agg(jsonb_build_object('id', ?, 'influencer_handle', ?))", unquote(influencer).id, unquote(influencer).influencer_handle)
end
end
You could also fetch the necessary data and do the group by in Elixir using Enum.group_by/2. This would be much more elegant but may be less performant than the above, depending on how optimized PostgreSQL's JSON handling is for the above query.
from(ip in InfluencerPlatform, select: map(ip, [:id, :name, :influencer_handle]))
|> Repo.all
|> Enum.group_by(fn ip -> ip.name end)
# Delete `:name` from each map.
|> Enum.map(fn {k, v} ->
{k, Enum.map(v, &Map.delete(&1, :name))}
end)
|> Map.new
I'm trying to write simple rest API. I use Database.SQLite.Simple, Scotty and Aeson. I have a problem with DB query inside ScottyM() route function. When I have something like this
routes :: [User] -> ScottyM ()
routes allUsers = do
get "/users/:id" $ do
id <- param "id"
json ([x | x <- allUsers, userId x == id] !! 0)
main = do
conn <- open "data.db"
users <- ( query_ conn "select id, first_name, second_name, team from users" :: IO [User] )
scotty 3000 (routes users)
everything works fine, but in this scenario allUsers will get updated only once at server start. I want to query every time anyone ask about it. So I wrote this:
routes :: Connection -> ScottyM ()
routes conn= do
get "/users/:id" $ do
id <- param "id"
users <- ( query_ conn "select id, first_name, second_name, team from users" :: IO [User] )
json (users !! id)
main = do
conn <- open "data.db"
scotty 3000 (routes conn)
I get error
Couldn't match expected type ‘Web.Scotty.Internal.Types.ActionT
Text IO [a0]’
with actual type ‘IO [User]’
In a stmt of a 'do' block:
users <- (query_
conn "select id, first_name, second_name, team from users" ::
IO [User])
In the second argument of ‘($)’, namely
‘do { id <- param "id";
users <- (query_
conn "select id, first_name, second_name, team from users" ::
IO [User]);
json (users !! id) }’
In a stmt of a 'do' block:
get "/users/:id"
$ do { id <- param "id";
users <- (query_
conn "select id, first_name, second_name, team from users" ::
IO [User]);
json (users !! id) }
how to fix that? Also how can I pass arguments to sql query if I want for example select * from users where id = id from parameters?
The get function's second argument is of type ActionM. If you investigate further, you'll see this is just shorthand for the more complicated looking ActionT, with e as Text and m as IO.
Because your do block is of this type, you can't call IO functions directly. You need to use liftIO to get the right type. So just adding a liftIO before the query_ call should fix it.
There's an example called using scotty with IO in the project wiki which shows this.
I know that you can ask ActiveRecord to list tables in console using:
ActiveRecord::Base.connection.tables
Is there a command that would list the columns in a given table?
This will list the column_names from a table
Model.column_names
e.g. User.column_names
This gets the columns, not just the column names and uses ActiveRecord::Base::Connection, so no models are necessary. Handy for quickly outputting the structure of a db.
ActiveRecord::Base.connection.tables.each do |table_name|
puts table_name
ActiveRecord::Base.connection.columns(table_name).each do |c|
puts "- #{c.name}: #{c.type} #{c.limit}"
end
end
Sample output: http://screencast.com/t/EsNlvJEqM
Using rails three you can just type the model name:
> User
gives:
User(id: integer, name: string, email: string, etc...)
In rails four, you need to establish a connection first:
irb(main):001:0> User
=> User (call 'User.connection' to establish a connection)
irb(main):002:0> User.connection; nil #call nil to stop repl spitting out the connection object (long)
=> nil
irb(main):003:0> User
User(id: integer, name: string, email: string, etc...)
If you are comfortable with SQL commands, you can enter your app's folder and run rails db, which is a brief form of rails dbconsole. It will enter the shell of your database, whether it is sqlite or mysql.
Then, you can query the table columns using sql command like:
pragma table_info(your_table);
complementing this useful information, for example using rails console o rails dbconsole:
Student is my Model, using rails console:
$ rails console
> Student.column_names
=> ["id", "name", "surname", "created_at", "updated_at"]
> Student
=> Student(id: integer, name: string, surname: string, created_at: datetime, updated_at: datetime)
Other option using SQLite through Rails:
$ rails dbconsole
sqlite> .help
sqlite> .table
ar_internal_metadata relatives schools
relationships schema_migrations students
sqlite> .schema students
CREATE TABLE "students" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "surname" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
Finally for more information.
sqlite> .help
Hope this helps!
You can run rails dbconsole in you command line tool to open sqlite console. Then type in .tables to list all the tables and .fullschema to get a list of all tables with column names and types.
To list the columns in a table I usually go with this:
Model.column_names.sort.
i.e. Orders.column_names.sort
Sorting the column names makes it easy to find what you are looking for.
For more information on each of the columns use this:
Model.columns.map{|column| [column.name, column.sql_type]}.to_h.
This will provide a nice hash.
for example:
{
id => int(4),
created_at => datetime
}
For a more compact format, and less typing just:
Portfolio.column_types
I am using rails 6.1 and have built a simple rake task for this.
You can invoke this from the cli using rails db:list[users] if you want a simple output with field names. If you want all the details then do rails db:list[users,1].
I constructed this from this question How to pass command line arguments to a rake task about passing command line arguments to rake tasks. I also built on #aaron-henderson's answer above.
# run like `rails db:list[users]`, `rails db:list[users,1]`, `RAILS_ENV=development rails db:list[users]` etc
namespace :db do
desc "list fields/details on a model"
task :list, [:model, :details] => [:environment] do |task, args|
model = args[:model]
if !args[:details].present?
model.camelize.constantize.column_names.each do |column_name|
puts column_name
end
else
ActiveRecord::Base.connection.tables.each do |table_name|
next if table_name != model.underscore.pluralize
ActiveRecord::Base.connection.columns(table_name).each do |c|
puts "Name: #{c.name} | Type: #{c.type} | Default: #{c.default} | Limit: #{c.limit} | Precision: #{c.precision} | Scale: #{c.scale} | Nullable: #{c.null} "
end
end
end
end
end