cpjk / canary

:hatching_chick: Elixir authorization and resource-loading library for Plug applications.
MIT License
473 stars 52 forks source link

does canary integrate with guardian db? #51

Closed devekko closed 7 years ago

devekko commented 7 years ago

hey there

am wondering if Canary works ok with Guardian DB?

this is my first Phoenix app and so am unsure exactly if this is a Canary issue BUT

when my controller is only loading resources via Canary I am ok

will follow-up with file info

been going round in circles on this for a few days now

[info] GET /spaces
[debug] QUERY OK source="guardian_tokens" db=0.7ms
SELECT g0."jti", g0."typ", g0."aud", g0."iss", g0."sub", g0."exp", g0."jwt", g0."claims", g0."inserted_at", g0."updated_at" FROM "guardian_tokens" AS g0 WHERE ((g0."jti" = $1) AND (g0."aud" = $2)) ["bbe8075b-b204-4cd3-bb04-8871dc18e7d8", "User:1"]
[debug] QUERY OK source="users" db=0.3ms queue=0.3ms
SELECT u0."id", u0."name", u0."email", u0."bio", u0."zipcode", u0."address", u0."phone", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
[debug] Processing by SavesonomacountyOrg.SpaceController.index/2
  Parameters: %{}
  Pipelines: [:browser, :browser_auth, :require_login]
[debug] QUERY OK source="spaces" db=0.2ms
SELECT s0."id", s0."name", s0."user_id", s0."inserted_at", s0."updated_at" FROM "spaces" AS s0 []
[debug] QUERY OK source="spaces" db=0.1ms
SELECT s0."id", s0."name", s0."user_id", s0."inserted_at", s0."updated_at" FROM "spaces" AS s0 []

when my controller is trying to authorize I get Guardian DB errors


[error] #PID<0.1392.0> running SavesonomacountyOrg.Endpoint terminated
Server: website.io:4000 (http)
Request: GET /spaces
** (exit) an exception was raised:
    ** (KeyError) key :guardian_default_resource not found in: %{current_user: %SavesonomacountyOrg.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, address: nil, authorizations: #Ecto.Association.NotLoaded<association :authorizations is not loaded>, bio: nil, email: "niccolo.roberts@gmail.com", id: 1, inserted_at: #Ecto.DateTime<2016-11-06 21:46:22>, name: "Nicholas Roberts", phone: nil, spaces: #Ecto.Association.NotLoaded<association :spaces is not loaded>, updated_at: #Ecto.DateTime<2016-11-06 21:46:22>, zipcode: nil}, spaces: [%SavesonomacountyOrg.Space{__meta__: #Ecto.Schema.Metadata<:loaded, "spaces">, id: 2, inserted_at: #Ecto.DateTime<2016-11-06 21:58:34>, name: "rr2", updated_at: #Ecto.DateTime<2016-11-06 21:58:44>, user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1}]}
        (elixir) lib/map.ex:164: Map.fetch!/2
        lib/canary/plugs.ex:193: Canary.Plugs._authorize_resource/2
        lib/canary/plugs.ex:186: Canary.Plugs.authorize_resource/2
        lib/canary/plugs.ex:267: Canary.Plugs._load_and_authorize_resource/2
        (savesonomacounty_org) web/controllers/space_controller.ex:1: SavesonomacountyOrg.SpaceController.phoenix_controller_pipeline/2
        (savesonomacounty_org) lib/savesonomacounty_org/endpoint.ex:1: SavesonomacountyOrg.Endpoint.instrument/4
        (savesonomacounty_org) lib/phoenix/router.ex:261: SavesonomacountyOrg.Router.dispatch/2
        (savesonomacounty_org) web/router.ex:1: SavesonomacountyOrg.Router.do_call/2
        (savesonomacounty_org) lib/savesonomacounty_org/endpoint.ex:1: SavesonomacountyOrg.Endpoint.phoenix_pipeline/1
        (savesonomacounty_org) lib/plug/debugger.ex:123: SavesonomacountyOrg.Endpoint."call (overridable 3)"/2
        (savesonomacounty_org) lib/savesonomacounty_org/endpoint.ex:1: SavesonomacountyOrg.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
cpjk commented 7 years ago

@devekko it looks like canary is looking for the current user to authorize under conn.assigns[:guardian_default_resource], but you have loaded the current user under conn.assigns[:current_user]. Check out the line in question in canary (from your stack trace).

cpjk commented 7 years ago

IIRC, by default Guardian loads the current user under conn.assigns[:guardian_default_user], so you just need to configure canary to look there. The canary README explains how to do this, and you can also check out this line in this small project that does the same thing.

cpjk commented 7 years ago

Also, I remember seeing this blog post about using canary with guardian and phoenix, but it's been a while since I read it, and I can't remember whether or not his approach was ideal :smile_cat:.

devekko commented 7 years ago

hmm, actually its

[info] GET /spaces
[debug] QUERY OK source="guardian_tokens" db=3.3ms
SELECT g0."jti", g0."typ", g0."aud", g0."iss", g0."sub", g0."exp", g0."jwt", g0."claims", g0."inserted_at", g0."updated_at" FROM "guardian_tokens" AS g0 WHERE ((g0."jti" = $1) AND (g0."aud" = $2)) ["bbe8075b-b204-4cd3-bb04-8871dc18e7d8", "User:1"]
[debug] QUERY OK source="users" db=1.3ms queue=0.2ms
SELECT u0."id", u0."name", u0."email", u0."bio", u0."zipcode", u0."address", u0."phone", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
[debug] Processing by SavesonomacountyOrg.SpaceController.index/2
  Parameters: %{}
  Pipelines: [:browser, :browser_auth, :require_login]
[debug] QUERY OK source="spaces" db=0.8ms queue=0.2ms
SELECT s0."id", s0."name", s0."user_id", s0."inserted_at", s0."updated_at" FROM "spaces" AS s0 []
[info] Sent 500 in 24ms
[error] #PID<0.471.0> running SavesonomacountyOrg.Endpoint terminated
Server: website.io:4000 (http)
Request: GET /spaces
** (exit) an exception was raised:
    ** (KeyError) key :guardian_default_resource not found in: %{current_user: %SavesonomacountyOrg.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, address: nil, authorizations: #Ecto.Association.NotLoaded<association :authorizations is not loaded>, bio: nil, email: "niccolo.roberts@gmail.com", id: 1, inserted_at: #Ecto.DateTime<2016-11-06 21:46:22>, name: "Nicholas Roberts", phone: nil, spaces: #Ecto.Association.NotLoaded<association :spaces is not loaded>, updated_at: #Ecto.DateTime<2016-11-06 21:46:22>, zipcode: nil}, spaces: [%SavesonomacountyOrg.Space{__meta__: #Ecto.Schema.Metadata<:loaded, "spaces">, id: 2, inserted_at: #Ecto.DateTime<2016-11-06 21:58:34>, name: "rr2", updated_at: #Ecto.DateTime<2016-11-06 21:58:44>, user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1}]}
        (elixir) lib/map.ex:164: Map.fetch!/2
        lib/canary/plugs.ex:193: Canary.Plugs._authorize_resource/2
        lib/canary/plugs.ex:186: Canary.Plugs.authorize_resource/2
        lib/canary/plugs.ex:267: Canary.Plugs._load_and_authorize_resource/2
        (savesonomacounty_org) web/controllers/space_controller.ex:3: SavesonomacountyOrg.SpaceController.phoenix_controller_pipeline/2
        (savesonomacounty_org) lib/savesonomacounty_org/endpoint.ex:1: SavesonomacountyOrg.Endpoint.instrument/4
        (savesonomacounty_org) lib/phoenix/router.ex:261: SavesonomacountyOrg.Router.dispatch/2
        (savesonomacounty_org) web/router.ex:1: SavesonomacountyOrg.Router.do_call/2
        (savesonomacounty_org) lib/savesonomacounty_org/endpoint.ex:1: SavesonomacountyOrg.Endpoint.phoenix_pipeline/1
        (savesonomacounty_org) lib/plug/debugger.ex:123: SavesonomacountyOrg.Endpoint."call (overridable 3)"/2
        (savesonomacounty_org) lib/savesonomacounty_org/endpoint.ex:1: SavesonomacountyOrg.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
devekko commented 7 years ago

that blog http://learningelixir.joekain.com/using-guardian-and-canary-with-phoenix/ directed me to use Canary in the first place

as far as I can tell I have assembled a relatively undocumented mix.exs

  defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:canary, github: "cpjk/canary"},
     {:guardian, "~> 0.13.0"},
     {:guardian_db, "~> 0.7"},
     {:ueberauth, github: "ueberauth/ueberauth", override: true},
     {:ueberauth_github, "~>0.4.0"},
     {:ueberauth_identity, "~>0.2.3"},
     {:ueberauth_slack, "~>0.4.0"},
     {:ueberauth_google, "~> 0.4.0"},
     {:ueberauth_facebook, "~> 0.5.0"},
     {:comeonin, "~> 2.6"}]
  end
devekko commented 7 years ago

I have all the config as documented, I think its guardian db

it might also be abilities.ex

devekko commented 7 years ago

lib/abilities.ex

defmodule SavesonomacountyOrg.Abilities do

  alias SavesonomacountyOrg.User

  defimpl Canada.Can, for: User do

    def can?(%User{}, :index, Space), do: true

    def can?(%User{}, action, Space)
      when action in [:new, :create], do: true

    def can?(%User{}, _, _), do: false
  end
end

config/config.exs

Guardian configuration

config :guardian, Guardian,
  issuer: "SavesonomacountyOrg.#{Mix.env}",
  ttl: {30, :days},
  verify_issuer: true,
  serializer: SavesonomacountyOrg.GuardianSerializer,
  secret_key: to_string(Mix.env),
  hooks: GuardianDb,
  permissions: %{
    default: [
      :read_profile,
      :write_profile,
      :read_token,
      :revoke_token,
    ],
  }

  config :guardian_db, GuardianDb,
    repo: SavesonomacountyOrg.Repo,
    sweep_interval: 60 # 60 minutes

  config :canary,
    repo: SavesonomacountyOrg.Repo,
    current_user: :guardian_default_resource
devekko commented 7 years ago

will open source the site during week, I was going to back out and go with Coherence and Ex Admin but I am too stubborn

cpjk commented 7 years ago

@devekko I would stick an IEx.pry binding before the canary plug in your controller, and check the contents of conn.assigns. From your stack trace, it looks like you are storing the user under conn.assigns[:current_user] instead of conn.assigns[:guardian_default_resource]

devekko commented 7 years ago
pry(7)> conn.assigns                          
%{current_user: %SavesonomacountyOrg.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   address: nil,
   authorizations: #Ecto.Association.NotLoaded<association :authorizations is not loaded>,
   bio: nil, email: "niccolo.roberts+admin@gmail.com", id: 2,
   inserted_at: #Ecto.DateTime<2016-11-06 22:10:17>, name: "n", phone: nil,
   spaces: #Ecto.Association.NotLoaded<association :spaces is not loaded>,
   updated_at: #Ecto.DateTime<2016-11-06 22:10:17>, zipcode: nil},
  spaces: [%SavesonomacountyOrg.Space{__meta__: #Ecto.Schema.Metadata<:loaded, "spaces">,
    id: 2, inserted_at: #Ecto.DateTime<2016-11-06 21:58:34>, name: "rr2",
    updated_at: #Ecto.DateTime<2016-11-06 21:58:44>,
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1}]}
cpjk commented 7 years ago

If you look at the output of the first command, you'll see current_user. In the second and third you are passing a variable to the [] operator instead of a symbol. Use :current_user instead of current_user

devekko commented 7 years ago

thanks, like this?

pry(6)> conn.assigns[:current_user]            
%SavesonomacountyOrg.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
 address: nil,
 authorizations: #Ecto.Association.NotLoaded<association :authorizations is not loaded>,
 bio: nil, email: "niccolo.roberts@gmail.com", id: 1,
 inserted_at: #Ecto.DateTime<2016-11-06 21:46:22>, name: "Nicholas Roberts",
 phone: nil,
 spaces: #Ecto.Association.NotLoaded<association :spaces is not loaded>,
 updated_at: #Ecto.DateTime<2016-11-06 21:46:22>, zipcode: nil}
pry(7)> conn.assigns[:guardian_default_resource]
nil 
cpjk commented 7 years ago

Yes.

devekko commented 7 years ago
pry(8)> conn                                    
%Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...},
 assigns: %{current_user: %SavesonomacountyOrg.User{__meta__: #Ecto.Schema.Metadata<:loaded, 
cpjk commented 7 years ago

If you read your comment here https://github.com/cpjk/canary/issues/51#issuecomment-258736323 you can see that there is no user where you told canary to look for it. You should configure canary to look for the current user under conn.assigns[:current_user], since that is where you are putting it.

devekko commented 7 years ago

thanks, I think that fixed it !

I think my error was to have changed default canary config to guardian_default_resource AND have a Guardian helper module as in the blog posts


defmodule MyApp.Plug.CurrentUser do
  def init(opts), do: opts

  def call(conn, _opts) do
    current_user = Guardian.Plug.current_resource(conn)
    Plug.Conn.assign(conn, :current_user, current_user)
  end
end

I think I kind of cancelled myself out

now, its working (I think)

great, closing