Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Adzz committed Oct 29, 2024
1 parent 443813e commit ad5ec4c
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 70 deletions.
3 changes: 2 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[
import_deps: [:phoenix],
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
plugins: [Phoenix.LiveView.HTMLFormatter],
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
]
4 changes: 2 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ config :logger, :console,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

# Placeholder for Cognito authentication, defined for real in environment configs
# Placeholder for Keycloak authentication, defined for real in environment configs
config :ueberauth, Ueberauth,
providers: [
cognito: {DocumentViewerWeb.Ueberauth.Strategy.Fake, []}
keycloak: {DocumentViewerWeb.Ueberauth.Strategy.Fake, []}
]

# Import environment specific config. This must remain at the bottom
Expand Down
23 changes: 16 additions & 7 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ end
if config_env() == :prod do
config :document_viewer, DocumentViewerWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE")

keycloak_opts = [
client_id: System.fetch_env!("KEYCLOAK_CLIENT_ID"),
client_secret: System.fetch_env!("KEYCLOAK_CLIENT_SECRET")
]

config(:ueberauth_oidcc,
issuers: [%{name: :keycloak_issuer, issuer: System.fetch_env!("KEYCLOAK_ISSUER")}],
providers: [keycloak: keycloak_opts]
)

config(:ueberauth, Ueberauth,
keycloak:
{Ueberauth.Strategy.Oidcc,
issuer: :keycloak_issuer, userinfo: true, uid_field: "email", scopes: ~w(openid email)}
)
end

if guardian_secret_key = System.get_env("GUARDIAN_SECRET_KEY") do
config :document_viewer, DocumentViewerWeb.AuthManager, secret_key: guardian_secret_key
end

config :ueberauth, Ueberauth.Strategy.Cognito,
auth_domain: System.get_env("COGNITO_DOMAIN"),
client_id: System.get_env("COGNITO_CLIENT_ID"),
client_secret: System.get_env("COGNITO_CLIENT_SECRET"),
user_pool_id: System.get_env("COGNITO_USER_POOL_ID"),
aws_region: System.get_env("COGNITO_AWS_REGION")
7 changes: 7 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

## Single Sign-on configuration.
### Used for local dev, cloud environments make use of Terraform env vars
# export KEYCLOAK_ISSUER=
# export KEYCLOAK_CLIENT_ID=
# export KEYCLOAK_CLIENT_SECRET=
# export KEYCLOAK_IDP_HINT=oidc-mbta-entraid
13 changes: 12 additions & 1 deletion lib/document_viewer_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ defmodule DocumentViewerWeb do
below. Instead, define any helper function in modules
and import those modules here.
"""
def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)

def controller do
quote do
use Phoenix.Controller, namespace: DocumentViewerWeb

import Plug.Conn
import DocumentViewerWeb.Gettext
alias DocumentViewerWeb.Router.Helpers, as: Routes
unquote(verified_routes())
end
end

Expand Down Expand Up @@ -69,6 +70,16 @@ defmodule DocumentViewerWeb do
import DocumentViewerWeb.ErrorHelpers
import DocumentViewerWeb.Gettext
alias DocumentViewerWeb.Router.Helpers, as: Routes
unquote(verified_routes())
end
end

def verified_routes do
quote do
use Phoenix.VerifiedRoutes,
endpoint: DocumentViewerWeb.Endpoint,
router: DocumentViewerWeb.Router,
statics: DocumentViewerWeb.static_paths()
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/document_viewer_web/controllers/auth_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ defmodule DocumentViewerWeb.AuthController do

plug(Ueberauth)

alias DocumentViewerWeb.{AuthManager, UserActionLogger}
alias DocumentViewerWeb.AuthManager
alias DocumentViewerWeb.Router.Helpers
alias DocumentViewerWeb.UserActionLogger

# So this gotta changE?
def request(conn, %{"provider" => provider}) when provider != "cognito" do
send_resp(conn, 404, "Not Found")
end
Expand Down
2 changes: 1 addition & 1 deletion lib/document_viewer_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule DocumentViewerWeb.Endpoint do
at: "/",
from: :document_viewer,
gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt)
only: DocumentViewerWeb.static_paths()

# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
Expand Down
18 changes: 5 additions & 13 deletions lib/document_viewer_web/ensure_document_viewer_group.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
defmodule DocumentViewerWeb.EnsureDocumentViewerGroup do
defmodule DocumentViewerWeb.EnsureDocumentViewerRole do
@moduledoc """
Verify that the user is in our Cognito group.
Verify that the user has the right keycloak role.
"""

import Plug.Conn

def init(opts) do
Keyword.put_new(
opts,
:document_viewer_group,
Application.get_env(:document_viewer, :cognito_group)
)
end

def call(conn, opts) do
def call(conn, _opts) do
with %{"groups" => groups} <- Guardian.Plug.current_claims(conn),
true <- is_list(groups),
document_viewer_group = Keyword.fetch!(opts, :document_viewer_group),
true <- document_viewer_group in groups do
# TODO check
true <- "admin" in groups do
conn
else
_ ->
Expand Down
33 changes: 33 additions & 0 deletions lib/document_viewer_web/plugs/ensure_role.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule DocumentViewerWeb.Plugs.EnsureRole do
require Plug.Conn

@moduledoc """
Checks whether the user in the assigns has ANY of the given roles, redirects if they do
not. This is useful for scoping parts of the app to the specific roles. For liveviews you
should also use the `DocumentViewerWeb.RequireRole` hook.
"""
require Logger
use DocumentViewerWeb, :controller

@impl true
def init(opts), do: opts

@impl true
def call(conn, %{roles: roles}) do
if Enum.any?(roles, fn role -> role in conn.assigns.current_user.roles end) do
conn
else
# If a user gets a failure from Ueberauth, we want to redirect them away from this site.
# Since everything on this site requires authorization, they will get trapped
# in an infinite loop of redirects otherwise.
Logger.error(
"Ueberauth error: user #{conn.assigns.current_user.id} tried to log in " <>
"without correct user permissions."
)

conn
|> Guardian.Plug.sign_out(AuthManager, [])
|> redirect(external: "https://www.mbta.com")
end
end
end
23 changes: 23 additions & 0 deletions lib/document_viewer_web/plugs/mount_current_user.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule DocumentViewerWeb.Plugs.MountCurrentUser do
@moduledoc """
This sets the logged in user in the assigns. Useful for subsequent plugs like checking user roles.
Also sets a sentry context if the user exists.
"""

use DocumentViewerWeb, :controller
require Plug.Conn

@impl true
def init(opts), do: opts

@impl true
def call(conn, _args) do
case Guardian.Plug.current_resource(conn) do
%{user: current_user} ->
Plug.Conn.assign(conn, :current_user, current_user)

_ ->
Plug.Conn.assign(conn, :current_user, nil)
end
end
end
7 changes: 4 additions & 3 deletions lib/document_viewer_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ defmodule DocumentViewerWeb.Router do
plug Guardian.Plug.EnsureAuthenticated
end

pipeline :ensure_document_viewer_group do
plug DocumentViewerWeb.EnsureDocumentViewerGroup
pipeline :ensure_document_viewer_role do
plug(DocumentViewerWeb.Plugs.MountCurrentUser)
plug(DocumentViewerWeb.Plugs.EnsureRole, %{roles: [:admin]})
end

pipeline :browser do
Expand Down Expand Up @@ -46,7 +47,7 @@ defmodule DocumentViewerWeb.Router do
:browser,
:auth,
:ensure_auth,
:ensure_document_viewer_group
:ensure_document_viewer_role
]

get("/", QueryController, :new)
Expand Down
15 changes: 10 additions & 5 deletions lib/document_viewer_web/ueberauth/strategy/fake.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ defmodule DocumentViewerWeb.Ueberauth.Strategy.Fake do
@moduledoc """
Mock Ueberauth strategy for development.
"""

use DocumentViewerWeb, :verified_routes
use Ueberauth.Strategy
@document_viewer_role "document_viewer_admin"

@impl Ueberauth.Strategy
def handle_request!(conn) do
conn
|> redirect!("/auth/cognito/callback")
|> redirect!(~p"/auth/keycloak/callback")
|> halt()
end

Expand All @@ -28,8 +29,7 @@ defmodule DocumentViewerWeb.Ueberauth.Strategy.Fake do
token: "fake_access_token",
refresh_token: "fake_refresh_token",
expires: true,
expires_at: System.system_time(:second) + 60 * 60,
other: %{groups: [Application.get_env(:document_viewer, :cognito_group)]}
expires_at: System.system_time(:second) + 60 * 60
}
end

Expand All @@ -40,7 +40,12 @@ defmodule DocumentViewerWeb.Ueberauth.Strategy.Fake do

@impl Ueberauth.Strategy
def extra(_conn) do
%Ueberauth.Auth.Extra{raw_info: %{}}
%Ueberauth.Auth.Extra{
raw_info: %{
claims: %{"aud" => "fake_aud"},
userinfo: %{"resource_access" => %{"fake_aud" => %{"roles" => [@document_viewer_role]}}}
}
}
end

@impl Ueberauth.Strategy
Expand Down
24 changes: 13 additions & 11 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,28 @@ defmodule DocumentViewer.MixProject do
# Type `mix help deps` for examples and options.
defp deps do
[
{:phoenix, "~> 1.5.7"},
{:phoenix_html, "~> 2.11"},
{:ecto, "~> 3.5"},
{:phoenix_ecto, "~> 4.2"},
{:phoenix_live_dashboard, "~> 0.4"},
{:telemetry_metrics, "~> 0.4"},
{:telemetry_poller, "~> 0.4"},
{:phoenix, "~> 1.7"},
{:phoenix_html, "~> 3.3"},
{:phoenix_view, "~> 2.0"},
{:ecto, "~> 3.12"},
{:phoenix_ecto, "~> 4.6"},
{:phoenix_live_dashboard, "~> 0.8"},
{:phoenix_live_reload, "~> 1.5", only: :dev},
{:phoenix_live_view, "~> 1.0.0-rc.7", override: true},
{:telemetry_metrics, "~> 1.0"},
{:telemetry_poller, "~> 1.1"},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:plug_cowboy, "~> 2.7"},
{:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"},
{:hackney, "~> 1.17"},
{:sweet_xml, "~> 0.7.3"},
{:ueberauth, "~> 0.6.3"},
{:ueberauth_cognito, "~> 0.2.0"},
{:ueberauth, "~> 0.10"},
{:ueberauth_oidcc, "~> 0.3"},
{:guardian, "~> 2.1"},
{:oauth2, "~> 2.0"},
{:logster, "~> 1.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
{:lcov_ex, "~> 0.2", only: [:dev, :test], runtime: false}
Expand Down
Loading

0 comments on commit ad5ec4c

Please sign in to comment.