Skip to content

Commit

Permalink
Re-enable graph feature with fixes to handle indirectly recursive def…
Browse files Browse the repository at this point in the history
…initions, eliminating infinite loops. Remove try/rescue usage in page_controller as it returns internal implementation details, which is a security risk. Instead letting the Phoenix framework (appropriately) return a generic Internal Server Error. Remove /guidelines route and handling as the related guidelines.html doesn't exist. (ocsf#93)
  • Loading branch information
rmouritzen-splunk authored May 25, 2024
1 parent 3e3e2ca commit c64fa6b
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 142 deletions.
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ config :schema_server, SchemaWeb.Endpoint,
config :logger, :console,
handle_otp_reports: true,
handle_sasl_reports: true,
format: "$time $metadata[$level] $message\n",
format: "$date $time [$level] $metadata $message\n",
metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
Expand Down
3 changes: 1 addition & 2 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ config :schema_server, SchemaWeb.Endpoint,

config :logger, :console,
level: :debug,
format: "[$level] $message\n $metadata\n",
# metadata: [:pid, :mfa, :line]
format: "[$level] $metadata $message\n",
metadata: [:mfa, :line]

# Set a higher stacktrace during development. Avoid configuring such
Expand Down
2 changes: 1 addition & 1 deletion config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import Config

# Do not print debug messages in production
config :logger,
level: :info
level: :warning

config :phoenix, :serve_endpoints, true
56 changes: 34 additions & 22 deletions lib/schema/graph.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,15 @@ defmodule Schema.Graph do
end

defp build_nodes(nodes, class) do
name = class.name

Map.get(class, :objects)
|> Enum.reduce(nodes, fn {_name, obj}, acc ->
# avoid recursive calls
if name != obj.name do
node = %{
id: make_id(obj.name, obj[:extension]),
label: obj.caption
}
node = %{
id: make_id(obj.name, obj[:extension]),
label: obj.caption
}

# Don't add class/object that is already added (present infinite loop)
if not nodes_member?(nodes, node) do
[node | acc]
else
acc
Expand All @@ -56,12 +54,15 @@ defmodule Schema.Graph do
defp make_id(name, nil) do
name
end

defp make_id(name, ext) do
# name
Path.join(ext, name)
end


defp nodes_member?(nodes, node) do
Enum.any?(nodes, fn n -> n.id == node.id end)
end

defp build_edges(class) do
objects = Map.new(class.objects)
build_edges([], class, objects)
Expand All @@ -72,20 +73,26 @@ defmodule Schema.Graph do
|> Enum.reduce(edges, fn {name, obj}, acc ->
case obj.type do
"object_t" ->
edge = %{
source: Atom.to_string(obj[:_source]),
group: obj[:group],
requirement: obj[:requirement] || "optional",
from: make_id(class.name, class[:extension]),
to: obj.object_type,
label: Atom.to_string(name)
}
|> add_profile(obj[:profile])
# For a recursive definition, we need to add the edge, creating the looping edge
# and then we don't want to continue searching this path.
recursive? = edges_member?(acc, obj)

edge =
%{
source: Atom.to_string(obj[:_source]),
group: obj[:group],
requirement: obj[:requirement] || "optional",
from: make_id(class.name, class[:extension]),
to: obj.object_type,
label: Atom.to_string(name)
}
|> add_profile(obj[:profile])

acc = [edge | acc]

# avoid recursive links
if class.name != obj.object_type do
# For recursive definitions, we've already added the edge creating the loop in the graph.
# There's no need to recurse further (avoid infinite loops).
if not recursive? do
o = objects[String.to_atom(obj.object_type)]
build_edges(acc, o, objects)
else
Expand All @@ -98,9 +105,14 @@ defmodule Schema.Graph do
end)
end

defp edges_member?(edges, obj) do
Enum.any?(edges, fn edge -> obj.object_type == edge.to end)
end

defp add_profile(edge, nil) do
edge
end

defp add_profile(edge, profile) do
Map.put(edge, :profile, profile)
end
Expand Down
191 changes: 78 additions & 113 deletions lib/schema_web/controllers/page_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,57 +15,38 @@ defmodule SchemaWeb.PageController do

alias SchemaWeb.SchemaController

# TODO: guidelines.html is missing (route is also commented out)
# @spec guidelines(Plug.Conn.t(), any) :: Plug.Conn.t()
# def guidelines(conn, params) do
# render(conn, "guidelines.html",
# extensions: Schema.extensions(),
# profiles: SchemaController.get_profiles(params)
# )
# end

@spec class_graph(Plug.Conn.t(), any) :: Plug.Conn.t()
def class_graph(conn, %{"id" => id} = params) do
# try do
# case SchemaWeb.SchemaController.class_ex(id, params) do
# nil ->
# send_resp(conn, 404, "Not Found: #{id}")

# class ->
# data = Schema.Graph.build(class)

# render(conn, "class_graph.html",
# extensions: Schema.extensions(),
# profiles: SchemaController.get_profiles(params),
# data: data
# )
# end
# rescue
# e -> send_resp(conn, 400, "Bad Request: #{inspect(e)}")
# end
send_resp(conn, 501, "The graph feature is temporarily disabled and will return soon.")
case SchemaWeb.SchemaController.class_ex(id, params) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

class ->
data = Schema.Graph.build(class)

render(conn, "class_graph.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: data
)
end
end

@spec object_graph(Plug.Conn.t(), any) :: Plug.Conn.t()
def object_graph(conn, %{"id" => id} = params) do
# try do
# case SchemaWeb.SchemaController.object_ex(id, params) do
# nil ->
# send_resp(conn, 404, "Not Found: #{id}")

# obj ->
# data = Schema.Graph.build(obj)

# render(conn, "object_graph.html",
# extensions: Schema.extensions(),
# profiles: SchemaController.get_profiles(params),
# data: data
# )
# end
# rescue
# e -> send_resp(conn, 400, "Bad Request: #{inspect(e)}")
# end
send_resp(conn, 501, "The graph feature is temporarily disabled and will return soon.")
case SchemaWeb.SchemaController.object_ex(id, params) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

obj ->
data = Schema.Graph.build(obj)

render(conn, "object_graph.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: data
)
end
end

@doc """
Expand Down Expand Up @@ -93,22 +74,18 @@ defmodule SchemaWeb.PageController do
extension -> "#{extension}/#{id}"
end

try do
data = SchemaController.get_profiles(params)
data = SchemaController.get_profiles(params)

case Map.get(data, name) do
nil ->
send_resp(conn, 404, "Not Found: #{name}")
case Map.get(data, name) do
nil ->
send_resp(conn, 404, "Not Found: #{name}")

profile ->
render(conn, "profile.html",
extensions: Schema.extensions(),
profiles: data,
data: sort_attributes(profile)
)
end
rescue
e -> send_resp(conn, 400, "Bad Request: #{inspect(e)}")
profile ->
render(conn, "profile.html",
extensions: Schema.extensions(),
profiles: data,
data: sort_attributes(profile)
)
end
end

Expand All @@ -127,22 +104,18 @@ defmodule SchemaWeb.PageController do
"""
@spec categories(Plug.Conn.t(), map) :: Plug.Conn.t()
def categories(conn, %{"id" => id} = params) do
try do
case SchemaController.category_classes(params) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

data ->
classes = sort_by(data[:classes], :uid)

render(conn, "category.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: Map.put(data, :classes, classes)
)
end
rescue
e -> send_resp(conn, 400, "Bad Request: #{inspect(e)}")
case SchemaController.category_classes(params) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

data ->
classes = sort_by(data[:classes], :uid)

render(conn, "category.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: Map.put(data, :classes, classes)
)
end
end

Expand Down Expand Up @@ -189,25 +162,21 @@ defmodule SchemaWeb.PageController do
def classes(conn, %{"id" => id} = params) do
extension = params["extension"]

try do
case Schema.class(extension, id) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

data ->
data =
data
|> sort_attributes()
|> Map.put(:key, Schema.Utils.to_uid(extension, id))

render(conn, "class.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: data
)
end
rescue
e -> send_resp(conn, 400, "Bad Request: #{inspect(e)}")
case Schema.class(extension, id) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

data ->
data =
data
|> sort_attributes()
|> Map.put(:key, Schema.Utils.to_uid(extension, id))

render(conn, "class.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: data
)
end
end

Expand All @@ -226,25 +195,21 @@ defmodule SchemaWeb.PageController do
"""
@spec objects(Plug.Conn.t(), map) :: Plug.Conn.t()
def objects(conn, %{"id" => id} = params) do
try do
case SchemaController.object(params) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

data ->
data =
data
|> sort_attributes()
|> Map.put(:key, Schema.Utils.to_uid(params["extension"], id))

render(conn, "object.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: data
)
end
rescue
e -> send_resp(conn, 400, "Bad Request: #{inspect(e)}")
case SchemaController.object(params) do
nil ->
send_resp(conn, 404, "Not Found: #{id}")

data ->
data =
data
|> sort_attributes()
|> Map.put(:key, Schema.Utils.to_uid(params["extension"], id))

render(conn, "object.html",
extensions: Schema.extensions(),
profiles: SchemaController.get_profiles(params),
data: data
)
end
end

Expand Down
2 changes: 0 additions & 2 deletions lib/schema_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ defmodule SchemaWeb.Router do
get "/object/graph/:extension/:id", PageController, :object_graph

get "/data_types", PageController, :data_types
# TODO: guidelines.html is missing (also commented out in PageController)
# get "/guidelines", PageController, :guidelines
end

# Other scopes may use custom stacks.
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
defmodule Schema.MixProject do
use Mix.Project

@version "2.70.3"
@version "2.70.4"

def project do
build = System.get_env("GITHUB_RUN_NUMBER") || "SNAPSHOT"
Expand Down

0 comments on commit c64fa6b

Please sign in to comment.