diff --git a/.github/workflows/publish_hex.yml b/.github/workflows/publish_hex.yml index 0011197..2adeaaa 100644 --- a/.github/workflows/publish_hex.yml +++ b/.github/workflows/publish_hex.yml @@ -2,7 +2,7 @@ name: Publish package to Hex 📦 on: push: tags: - - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]*" jobs: publish: diff --git a/lib/arke_server/controllers/arke_controller.ex b/lib/arke_server/controllers/arke_controller.ex index 8128ed3..653851a 100644 --- a/lib/arke_server/controllers/arke_controller.ex +++ b/lib/arke_server/controllers/arke_controller.ex @@ -88,6 +88,49 @@ defmodule ArkeServer.ArkeController do end end + @doc """ + Bulk Creates units + """ + def create_bulk(%Plug.Conn{body_params: params} = conn, %{"arke_id" => id}) do + # all arkes struct and gen server are on :arke_system so it won't be changed to project + project = conn.assigns[:arke_project] + + arke = ArkeManager.get(String.to_atom(id), project) + + # TODO handle query parameter with plugs + load_links = Map.get(conn.query_params, "load_links", "false") == "true" + load_values = Map.get(conn.query_params, "load_values", "false") == "true" + load_files = Map.get(conn.query_params, "load_files", "false") == "true" + + case is_bulk_body_valid?(params) do + false -> + ResponseManager.send_resp(conn, 400, nil, "Invalid bulk body") + + true -> + QueryManager.create_bulk( + project, + arke, + params["data"], + runtime_data: %{conn: conn} + ) + |> case do + {:ok, inserted_count, units, errors} -> + ResponseManager.send_resp(conn, 200, %{ + content: + ArkeServer.Utils.Bulk.build_response_content(conn, inserted_count, units, errors) + }) + + {:error, error} -> + ResponseManager.send_resp(conn, 400, nil, error) + end + end + end + + defp is_bulk_body_valid?(%{"data" => data} = _params) when is_list(data), + do: Enum.all?(data, fn item -> is_map(item) end) + + defp is_bulk_body_valid?(params), do: false + # delete @doc """ Delete a unit @@ -97,11 +140,40 @@ defmodule ArkeServer.ArkeController do QueryManager.delete(project, conn.assigns[:unit]) |> case do - {:ok, nil} -> ResponseManager.send_resp(conn, 204) + {:ok, _} -> ResponseManager.send_resp(conn, 204) {:error, error} -> ResponseManager.send_resp(conn, 400, nil, error) end end + # delete bulk + @doc """ + Delete a list of units + """ + def delete_bulk(conn, %{"arke_id" => arke_id} = params) do + project = conn.assigns[:arke_project] + + permission = conn.assigns[:permission_filter] || %{filter: nil} + member = ArkeAuth.Guardian.Plug.current_resource(conn) + + unit_ids = Map.get(params, "id", []) + + existing_units = + QueryManager.query(project: project, arke: arke_id) + |> QueryFilters.apply_query_filters(permission.filter) + |> QueryFilters.apply_member_child_only(member, Map.get(permission, :child_only, false)) + |> QueryManager.where(id__in: unit_ids) + |> QueryManager.all() + + QueryManager.delete_bulk(project, existing_units) + |> case do + {:ok, _, _} -> + ResponseManager.send_resp(conn, 204) + + {:error, error} -> + ResponseManager.send_resp(conn, 400, nil, error) + end + end + @doc """ Get units """ diff --git a/lib/arke_server/controllers/oauth_controller.ex b/lib/arke_server/controllers/oauth_controller.ex index 5476de9..04b4054 100644 --- a/lib/arke_server/controllers/oauth_controller.ex +++ b/lib/arke_server/controllers/oauth_controller.ex @@ -23,7 +23,6 @@ defmodule ArkeServer.OAuthController do # Openapi request definition use ArkeServer.Openapi.Spec, module: ArkeServer.Openapi.OAuthControllerSpec - alias Arke.Boundary.GroupManager alias ArkeServer.ResponseManager @@ -49,13 +48,18 @@ defmodule ArkeServer.OAuthController do _params ) do project = conn.assigns[:arke_project] - case init_oauth_flow(project,auth, provider) do - {:ok, body,oauth_member} -> + + case init_oauth_flow(project, auth, provider) do + {:ok, body, oauth_member} -> member = QueryManager.get_by(project: project, id: oauth_member.id) - handle_member_login(conn,member) + handle_member_login(conn, member) ResponseManager.send_resp(conn, 200, %{content: body}) - {:error,[%{context: "auth", message: "unauthorized"}]=msg} -> ResponseManager.send_resp(conn, 401, msg) - {:error, msg} -> ResponseManager.send_resp(conn, 400, msg) + + {:error, [%{context: "auth", message: "unauthorized"}] = msg} -> + ResponseManager.send_resp(conn, 401, msg) + + {:error, msg} -> + ResponseManager.send_resp(conn, 400, msg) end end @@ -72,57 +76,82 @@ defmodule ArkeServer.OAuthController do end def handle_create_member( - %Plug.Conn{body_params: params}=conn, - %{"member" => member_id, "provider" => provider}=_all_params + %Plug.Conn{body_params: params} = conn, + %{"member" => member_id, "provider" => provider} = _all_params ) do user_resource = ArkeAuth.SSOGuardian.Plug.current_resource(conn) - user = QueryManager.get_by(project: :arke_system, arke_id: :user,id: user_resource.id) + user = QueryManager.get_by(project: :arke_system, arke_id: :user, id: user_resource.id) project = conn.assigns[:arke_project] provider_arke_id = String.to_existing_atom("oauth_#{provider}") - enable_sso_group = GroupManager.get(:enable_sso,project) + enable_sso_group = GroupManager.get(:enable_sso, project) # check if the given member_id is enable to sso login # check if the user has any oauth link # check if one of the oauth link has the arke_id equal to the given provider - with true <- member_id in (GroupManager.get_arke_list(enable_sso_group) |> Enum.map(fn ak -> to_string(ak.id)end)), - [_data] = link_list <- get_link(user,:child), - %Unit{}=_unit <- Enum.find(link_list, fn link_unit -> link_unit.arke_id == provider_arke_id end) do - case check_member(project,user) do + # there are no units associated with that provider or the provider does not exist + with true <- + member_id in (GroupManager.get_arke_list(enable_sso_group) + |> Enum.map(fn ak -> to_string(ak.id) end)), + [_data] = link_list <- get_link(user, :child), + %Unit{} = _unit <- + Enum.find(link_list, fn link_unit -> link_unit.arke_id == provider_arke_id end) do + case check_member(project, user) do # member does not exists so create one - {:ok,nil} -> - case create_member(project,user,params,member_id) do - {:ok,member} -> - {:ok, resource_member, access_token, refresh_token} = Auth.create_tokens(member,"default") - content = create_response_body(resource_member,access_token,refresh_token,false) - AuthController.mailer_module().signup(conn,resource_member, mode: "oauth",member: resource_member,response_body: content) - ResponseManager.send_resp(conn, 200, content) - err -> ResponseManager.send_resp(conn, 400, err) - end + {:ok, nil} -> + case create_member(project, user, params, member_id) do + {:ok, member} -> + {:ok, resource_member, access_token, refresh_token} = + Auth.create_tokens(member, "default") + + content = create_response_body(resource_member, access_token, refresh_token, false) + + AuthController.mailer_module().signup(conn, resource_member, + mode: "oauth", + member: resource_member, + response_body: content + ) + + ResponseManager.send_resp(conn, 200, content) + + err -> + ResponseManager.send_resp(conn, 400, err) + end + # member exists and it is active {:ok, resource_member, access_token, refresh_token} -> - content = create_response_body(resource_member,access_token,refresh_token,false) - AuthController.mailer_module().signup(conn,resource_member, mode: "oauth",member: resource_member,response_body: content) + content = create_response_body(resource_member, access_token, refresh_token, false) + + AuthController.mailer_module().signup(conn, resource_member, + mode: "oauth", + member: resource_member, + response_body: content + ) + ResponseManager.send_resp(conn, 200, content) + {:error, reason} -> {:error, reason} end - else nil -> # there are no units associated with that provider or the provider does not exist - # in any of the unit sso associated with the user + else + nil -> + # in any of the unit sso associated with the user {:error, msg} = Error.create(:sso, "invalid provider") ResponseManager.send_resp(conn, 400, msg) - false -> {:error, msg} = Error.create(:sso, "invalid member") - ResponseManager.send_resp(conn, 400, msg) # sso not enabled for the given member + + false -> + {:error, msg} = Error.create(:sso, "invalid member") + # sso not enabled for the given member + ResponseManager.send_resp(conn, 400, msg) end end def handle_create_member( - conn, + conn, _params ) do {:error, msg} = Error.create(:auth, "invalid token/provider") ResponseManager.send_resp(conn, 400, msg) end - # ------- Client Side ------- # ------- Using redirects ------- @@ -135,31 +164,36 @@ defmodule ArkeServer.OAuthController do _params ) do project = conn.assigns[:arke_project] - case init_oauth_flow(project,auth, provider) do - {:ok, body,_member} -> + + case init_oauth_flow(project, auth, provider) do + {:ok, body, _member} -> ResponseManager.send_resp(conn, 200, %{content: body}) - {:error, msg} -> ResponseManager.send_resp(conn, 400, msg) + + {:error, msg} -> + ResponseManager.send_resp(conn, 400, msg) end end # ------- end Using redirects ------- - defp init_oauth_flow(project,auth_info, provider) do + defp init_oauth_flow(project, auth_info, provider) do with {:ok, nil} <- check_provider(provider), {:ok, user} <- check_oauth(auth_info) do - case check_member(project,user) do - #if member does not exists creat a SSO token - {:ok,nil} -> {:ok, user, access_token, refresh_token} = Auth.create_tokens(user,"sso") - content = create_response_body(user,access_token,refresh_token,true) - {:ok, content,user} - # if exists and is active authenticate the user - {:ok, resource_member, access_token, refresh_token} -> - - content = create_response_body(resource_member,access_token,refresh_token,false) - {:ok, content,resource_member} - {:error, reason} -> - {:error, reason} - end + case check_member(project, user) do + # if member does not exists creat a SSO token + {:ok, nil} -> + {:ok, user, access_token, refresh_token} = Auth.create_tokens(user, "sso") + content = create_response_body(user, access_token, refresh_token, true) + {:ok, content, user} + + # if exists and is active authenticate the user + {:ok, resource_member, access_token, refresh_token} -> + content = create_response_body(resource_member, access_token, refresh_token, false) + {:ok, content, resource_member} + + {:error, reason} -> + {:error, reason} + end else {:error, reason} -> {:error, reason} @@ -200,31 +234,34 @@ defmodule ArkeServer.OAuthController do check_oauth_user(oauth_user_data, provider) end - defp create_response_body(resource,access_token,refresh_token,uncompleted_data) do + defp create_response_body(resource, access_token, refresh_token, uncompleted_data) do Map.merge(Arke.StructManager.encode(resource, type: :json), %{ access_token: access_token, refresh_token: refresh_token, - uncompleted_data: uncompleted_data, + uncompleted_data: uncompleted_data }) end - defp create_member(project,user,params,member_id) do - member_model = ArkeManager.get(String.to_atom(member_id),project) - member_data = Map.put(params,"arke_system_user", to_string(user.id)) |> Map.put("email",user.data.email) + defp create_member(project, user, params, member_id) do + member_model = ArkeManager.get(String.to_atom(member_id), project) + + member_data = + Map.put(params, "arke_system_user", to_string(user.id)) |> Map.put("email", user.data.email) + new_data = for {key, val} <- member_data, into: %{}, do: {String.to_atom(key), val} - QueryManager.create(project,member_model,new_data) + QueryManager.create(project, member_model, new_data) end defp create_user(user_data) do user_model = ArkeManager.get(:user, :arke_system) pwd = UUID.uuid4() updated_data = Map.put(user_data, :password, pwd) - email = Map.get(user_data,:email) + email = Map.get(user_data, :email) + case QueryManager.get_by(project: :arke_system, arke_id: :user, email: email) do nil -> QueryManager.create(:arke_system, user_model, updated_data) - user -> {:ok,user} + user -> {:ok, user} end - end defp create_link(parent_id, child_id, provider) do @@ -233,13 +270,13 @@ defmodule ArkeServer.OAuthController do }) end - defp get_link(unit,direction) do + defp get_link(unit, direction) do QueryManager.query(project: :arke_system) |> QueryManager.link(unit, - depth: 1, - direction: direction, - type: "oauth" - ) + depth: 1, + direction: direction, + type: "oauth" + ) |> QueryManager.all() end @@ -269,7 +306,7 @@ defmodule ArkeServer.OAuthController do oauth_unit -> # check if there is a link between the given oauth_unit and an user - case get_link(oauth_unit,:parent) do + case get_link(oauth_unit, :parent) do [] -> # create a user and connect the two with {:ok, user} <- create_user(oauth_user_data), @@ -290,17 +327,24 @@ defmodule ArkeServer.OAuthController do end end - defp check_member(project,user) do - case Auth.get_project_member(project,user) do - {:ok , member} -> Auth.create_tokens(Auth.format_member(member),"default") - {:error, [%{context: "auth", message: "member not exists"}]} -> {:ok,nil} # if not exists return {:ok,nil} - {:error, _msg} -> Error.create(:auth, "unauthorized") + defp check_member(project, user) do + case Auth.get_project_member(project, user) do + {:ok, member} -> + Auth.create_tokens(Auth.format_member(member), "default") + + # if not exists return {:ok,nil} + {:error, [%{context: "auth", message: "member not exists"}]} -> + {:ok, nil} + + {:error, _msg} -> + Error.create(:auth, "unauthorized") end end - defp handle_member_login(_conn,nil), do: nil - defp handle_member_login(conn,member) do + defp handle_member_login(_conn, nil), do: nil + + defp handle_member_login(conn, member) do AuthController.update_member_access_time(member) - AuthController.mailer_module().signin(conn,member, mode: "oauth") + AuthController.mailer_module().signin(conn, member, mode: "oauth") end end diff --git a/lib/arke_server/controllers/topology_controller.ex b/lib/arke_server/controllers/topology_controller.ex index 20eb1c8..6743056 100644 --- a/lib/arke_server/controllers/topology_controller.ex +++ b/lib/arke_server/controllers/topology_controller.ex @@ -1,14 +1,13 @@ defmodule ArkeServer.TopologyController do @moduledoc """ - Documentation for `ArkeServer.TopologyController`. - """ + Documentation for `ArkeServer.TopologyController`. + """ use ArkeServer, :controller # Openapi request definition use ArkeServer.Openapi.Spec, module: ArkeServer.Openapi.TopologyControllerSpec - alias Arke.{QueryManager, LinkManager, StructManager} alias Arke.Utils.ErrorGenerator, as: Error alias UnitSerializer @@ -19,10 +18,9 @@ defmodule ArkeServer.TopologyController do alias OpenApiSpex.{Operation, Reference} @doc """ - Get the unit linked to an Arke - """ + Get the unit linked to an Arke + """ def get_node(conn, %{"arke_id" => _arke_id, "arke_unit_id" => _id, "direction" => direction}) do - offset = Map.get(conn.query_params, "offset", nil) limit = Map.get(conn.query_params, "limit", nil) order = Map.get(conn.query_params, "order", []) @@ -51,24 +49,28 @@ defmodule ArkeServer.TopologyController do QueryManager.query(project: project) |> QueryManager.link(conn.assigns[:unit], - depth: depth, - direction: direction, - type: link_type - ) + depth: depth, + direction: direction, + type: link_type + ) |> QueryFilters.apply_query_filters(Map.get(conn.assigns, :filter)) end - def get_node_count(conn, %{"arke_id" => _arke_id, "arke_unit_id" => _id, "direction" => direction}) do - - count = handle_get_node_query(conn, direction) - |> QueryManager.count() + def get_node_count(conn, %{ + "arke_id" => _arke_id, + "arke_unit_id" => _id, + "direction" => direction + }) do + count = + handle_get_node_query(conn, direction) + |> QueryManager.count() ResponseManager.send_resp(conn, 200, count) end @doc """ - Link two unit together - """ + Link two unit together + """ def create_node(%Plug.Conn{body_params: params} = conn, %{ "arke_id" => arke_id, "arke_id_two" => arke_id_two, @@ -90,7 +92,11 @@ defmodule ArkeServer.TopologyController do ResponseManager.send_resp( conn, 201, - StructManager.encode(unit, load_links: load_links, load_values: load_values, type: :json) + StructManager.encode(unit, + load_links: load_links, + load_values: load_values, + type: :json + ) ) {:error, error} -> @@ -99,9 +105,30 @@ defmodule ArkeServer.TopologyController do end @doc """ - Update metadata of an existing link - """ + Create a link between two units in bulk. + """ + def create_node_bulk(%Plug.Conn{body_params: params} = conn, _) do + project = conn.assigns[:arke_project] + case(LinkManager.add_node_bulk(project, params["data"])) do + {:ok, inserted_count, units, errors} -> + IO.inspect(inserted_count, label: "inserted_count") + IO.inspect(units, label: "units") + IO.inspect(errors, label: "errors") + + ResponseManager.send_resp(conn, 200, %{ + content: + ArkeServer.Utils.Bulk.build_response_content(conn, inserted_count, units, errors) + }) + + {:error, error} -> + ResponseManager.send_resp(conn, 400, nil, error) + end + end + + @doc """ + Update metadata of an existing link + """ def update_node(%Plug.Conn{body_params: params} = conn, %{ "arke_unit_id" => parent_id, "link_id" => type, @@ -118,8 +145,8 @@ defmodule ArkeServer.TopologyController do end @doc """ - Delete a connection between two units - """ + Delete a connection between two units + """ def delete_node(%Plug.Conn{body_params: params} = conn, %{ "arke_id" => _arke_id, "arke_id_two" => _arke_id_two, @@ -141,8 +168,24 @@ defmodule ArkeServer.TopologyController do end @doc """ - Associate a parameter to an Arke - """ + Delete a connection between two units in bulk. + """ + def delete_node_bulk(%Plug.Conn{body_params: params} = conn, _) do + project = conn.assigns[:arke_project] + + LinkManager.delete_node_bulk(project, params["data"]) + |> case do + {:ok, _, _} -> + ResponseManager.send_resp(conn, 204) + + {:error, error} -> + ResponseManager.send_resp(conn, 400, nil, error) + end + end + + @doc """ + Associate a parameter to an Arke + """ def add_parameter(%Plug.Conn{body_params: params} = conn, %{ "arke_parameter_id" => parameter_id, "arke_id" => arke_id @@ -173,8 +216,8 @@ defmodule ArkeServer.TopologyController do end @doc """ - Update an associated parameter of an Arke - """ + Update an associated parameter of an Arke + """ def update_parameter(%Plug.Conn{body_params: params} = conn, %{ "arke_parameter_id" => parameter_id, "arke_id" => arke_id diff --git a/lib/arke_server/controllers/unit_controller.ex b/lib/arke_server/controllers/unit_controller.ex index 67c4ed4..35d2964 100644 --- a/lib/arke_server/controllers/unit_controller.ex +++ b/lib/arke_server/controllers/unit_controller.ex @@ -18,7 +18,6 @@ defmodule ArkeServer.UnitController do # Openapi request definition use ArkeServer.Openapi.Spec, module: ArkeServer.Openapi.UnitControllerSpec - alias Arke.{QueryManager, LinkManager, StructManager} alias Arke.Boundary.{ArkeManager, ParameterManager} alias UnitSerializer @@ -31,8 +30,8 @@ defmodule ArkeServer.UnitController do import ArkeServer.ArkeController, only: [data_as_klist: 1] @doc """ - Search units - """ + Search units + """ def search(conn, %{}) do project = conn.assigns[:arke_project] offset = Map.get(conn.query_params, "offset", 0) @@ -52,8 +51,8 @@ defmodule ArkeServer.UnitController do end @doc """ - Update an unit - """ + Update an unit + """ def update(%Plug.Conn{body_params: params} = conn, %{ "unit_id" => _unit_id, "arke_id" => _arke_id @@ -80,4 +79,42 @@ defmodule ArkeServer.UnitController do ResponseManager.send_resp(conn, 400, nil, error) end end + + @doc """ + Update units in bulk + """ + def update_bulk(%Plug.Conn{body_params: params} = conn, %{ + "arke_id" => id + }) do + project = conn.assigns[:arke_project] + # TODO handle query parameter with plugs + load_links = Map.get(conn.query_params, "load_links", "false") == "true" + load_values = Map.get(conn.query_params, "load_values", "false") == "true" + + arke = ArkeManager.get(String.to_atom(id), project) + + permission = conn.assigns[:permission_filter] || %{filter: nil} + member = ArkeAuth.Guardian.Plug.current_resource(conn) + + unit_ids = Enum.map(params["data"], fn unit -> Map.get(unit, "id") end) + + existing_units = + QueryManager.query(project: project, arke: arke.id) + |> QueryFilters.apply_query_filters(permission.filter) + |> QueryFilters.apply_member_child_only(member, Map.get(permission, :child_only, false)) + |> QueryManager.where(id__in: unit_ids) + |> QueryManager.all() + + QueryManager.update_bulk(project, arke, existing_units, params["data"]) + |> case do + {:ok, updated_count, units, errors} -> + ResponseManager.send_resp(conn, 200, %{ + content: + ArkeServer.Utils.Bulk.build_response_content(conn, updated_count, units, errors) + }) + + {:error, error} -> + ResponseManager.send_resp(conn, 400, nil, error) + end + end end diff --git a/lib/arke_server/oauth/providers/microsoft.ex b/lib/arke_server/oauth/providers/microsoft.ex index 988caa0..662e63b 100644 --- a/lib/arke_server/oauth/providers/microsoft.ex +++ b/lib/arke_server/oauth/providers/microsoft.ex @@ -9,24 +9,24 @@ defmodule ArkeServer.OAuth.Provider.Microsoft do token_data = conn.private[@private_oauth_key] %UserInfo{ - first_name: token_data["givenName"], - last_name: token_data["surname"], - email: token_data["mail"] + first_name: token_data["given_name"], + last_name: token_data["family_name"], + email: token_data["upn"] } - end def uid(conn) do - conn.private[@private_oauth_key]["id"] + conn.private[@private_oauth_key]["sub"] end def handle_cleanup(conn), do: put_private(conn, @private_oauth_key, nil) - - def handle_request(%Plug.Conn{body_params: %{"id_token" => token, "access_token"=> access_token}} = conn) do + def handle_request( + %Plug.Conn{body_params: %{"id_token" => token, "access_token" => access_token}} = conn + ) do with {:ok, claims} <- verify_token(token), - {:ok, data} <- get_user_data(access_token) do - put_private(conn, @private_oauth_key, data) + {:ok, access_token_claims} <- decode_access_token(access_token) do + put_private(conn, @private_oauth_key, access_token_claims) else {:error, msg} -> Plug.Conn.assign( @@ -39,6 +39,7 @@ defmodule ArkeServer.OAuth.Provider.Microsoft do def handle_request(conn) do {:error, msg} = Error.create(:auth, "token not found") + Plug.Conn.assign( conn, :arke_server_oauth_failure, @@ -46,22 +47,6 @@ defmodule ArkeServer.OAuth.Provider.Microsoft do ) end - defp get_user_data(access_token) do - url = "https://graph.microsoft.com/v1.0/me" - headers = [{"Authorization", "Bearer #{access_token}"}] - - case HTTPoison.get(url, headers) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - {:ok, Jason.decode!(body)} - - {:ok, %HTTPoison.Response{status_code: status_code}} -> - {:error, "Failed to verify token, status code: #{status_code}"} - - {:error, %HTTPoison.Error{reason: reason}} -> - {:error, reason} - end - end - defp verify_token(token) do with {:ok, claims} <- decode_and_verify(token), :ok <- validate_claims(claims) do @@ -73,11 +58,11 @@ defmodule ArkeServer.OAuth.Provider.Microsoft do defp decode_and_verify(token) do jwt = JOSE.JWT.peek(token) + case validate_signature(token) do :ok -> {:ok, jwt.fields} {:error, reason} -> {:error, reason} end - end defp validate_signature(jwt) do @@ -89,29 +74,44 @@ defmodule ArkeServer.OAuth.Provider.Microsoft do end end) - {:error, reason} -> {:error, reason} + {:error, reason} -> + {:error, reason} end end defp get_public_keys do uri = "https://login.microsoftonline.com/#{get_key("AZURE_TENANT_ID")}/discovery/v2.0/keys" + case HTTPoison.get(uri) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> keys = body |> Jason.decode!() |> Map.get("keys") {:ok, Enum.map(keys, &JOSE.JWK.from(&1))} - {:error, %HTTPoison.Error{reason: reason}} -> {:error, reason} + {:error, %HTTPoison.Error{reason: reason}} -> + {:error, reason} end end defp validate_claims(claims) do cond do - claims["iss"] != "https://login.microsoftonline.com/#{get_key("AZURE_TENANT_ID")}/v2.0" -> {:error, "Invalid issuer"} - claims["tid"] != get_key("AZURE_TENANT_ID") -> {:error, "Invalid tenant"} - DatetimeHandler.from_unix(Map.get(claims, "exp", 0)) < DatetimeHandler.now(:datetime) -> {:error, "Token expired"} - true -> :ok + claims["iss"] != "https://login.microsoftonline.com/#{get_key("AZURE_TENANT_ID")}/v2.0" -> + {:error, "Invalid issuer"} + + claims["tid"] != get_key("AZURE_TENANT_ID") -> + {:error, "Invalid tenant"} + + DatetimeHandler.from_unix(Map.get(claims, "exp", 0)) < DatetimeHandler.now(:datetime) -> + {:error, "Token expired"} + + true -> + :ok end end defp get_key(key), do: System.get_env(key, nil) + + defp decode_access_token(access_token) do + jwt = JOSE.JWT.peek(access_token) + {:ok, jwt.fields} + end end diff --git a/lib/arke_server/router.ex b/lib/arke_server/router.ex index 0a347be..70941e9 100644 --- a/lib/arke_server/router.ex +++ b/lib/arke_server/router.ex @@ -163,6 +163,7 @@ defmodule ArkeServer.Router do # UNIT put("/:arke_id/unit/:unit_id", UnitController, :update) + put("/:arke_id/bulk/unit", UnitController, :update_bulk) put("/:arke_id/parameter/:arke_parameter_id", TopologyController, :update_parameter) @@ -175,6 +176,7 @@ defmodule ArkeServer.Router do # -------- POST -------- post("/:arke_id/unit", ArkeController, :create) + post("/:arke_id/bulk/unit", ArkeController, :create_bulk) post("/:arke_id/parameter/:arke_parameter_id", TopologyController, :add_parameter) @@ -184,6 +186,9 @@ defmodule ArkeServer.Router do :create_node ) + post("/bulk/link", TopologyController, :create_node_bulk) + post("/bulk/link/:link_id", TopologyController, :create_node_bulk) + # -------- DELETE -------- delete( "/:arke_id/unit/:arke_unit_id/link/:link_id/:arke_id_two/unit/:unit_id_two", @@ -191,8 +196,12 @@ defmodule ArkeServer.Router do :delete_node ) + delete("/:arke_id/unit/bulk", ArkeController, :delete_bulk) delete("/:arke_id/unit/:unit_id", ArkeController, :delete) + delete("/bulk/link", TopologyController, :delete_node_bulk) + delete("/bulk/link/:link_id", TopologyController, :delete_node_bulk) + # -------- CALL FUNCTION -------- get("/:arke_id/function/:function_name", ArkeController, :call_arke_function) diff --git a/lib/arke_server/utils/bulk.ex b/lib/arke_server/utils/bulk.ex new file mode 100644 index 0000000..347b517 --- /dev/null +++ b/lib/arke_server/utils/bulk.ex @@ -0,0 +1,42 @@ +defmodule ArkeServer.Utils.Bulk do + alias Arke.StructManager + + def build_response_content(conn, count, valid, errors) do + load_links = Map.get(conn.query_params, "load_links", "false") == "true" + load_values = Map.get(conn.query_params, "load_values", "false") == "true" + load_files = Map.get(conn.query_params, "load_files", "false") == "true" + return_units = Map.get(conn.query_params, "return_units", "false") == "true" + + error_units = + Enum.map(errors, fn + {unit, unit_errors} -> + Map.put( + StructManager.encode(unit, + load_links: load_links, + load_values: load_values, + load_files: load_files, + type: :json + ), + "errors", + unit_errors + ) + + error -> + error + end) + + response = %{ + success_count: count, + error_count: length(error_units), + error_units: error_units + } + + case return_units do + true -> + Map.put(response, :units, StructManager.encode(valid, type: :json)) + + false -> + response + end + end +end diff --git a/lib/arke_server/utils/query_filters.ex b/lib/arke_server/utils/query_filters.ex index d7136eb..8b45232 100644 --- a/lib/arke_server/utils/query_filters.ex +++ b/lib/arke_server/utils/query_filters.ex @@ -64,20 +64,21 @@ defmodule ArkeServer.Utils.QueryFilters do remove_match(match, acc) end) |> String.split(",") - |> Enum.reduce(%{error: [],operator: []},fn x,acc -> + |> Enum.reduce(%{error: [], operator: []}, fn x, acc -> case get_operator(x) do {:ok, op} -> - Map.update(acc,:operator,[],fn old -> old ++ [op]end) + Map.update(acc, :operator, [], fn old -> old ++ [op] end) {:error, msg} -> - Map.update(acc,:error,[],fn old -> msg ++ old end) + Map.update(acc, :error, [], fn old -> msg ++ old end) end end) - errors = Map.get(operator_list,:error) - operators = Map.get(operator_list,:operator) - if length(errors) >0 do - {:error,errors} + errors = Map.get(operator_list, :error) + operators = Map.get(operator_list, :operator) + + if length(errors) > 0 do + {:error, errors} else filters = Enum.with_index(operators) @@ -124,8 +125,11 @@ defmodule ArkeServer.Utils.QueryFilters do case String.split(data, ",", parts: 2) do [parameter_id, value] -> case get_condition(conn, parameter_id, operator, value, negate) do - {:error, msg} -> {:error, msg} - {:ok, condition} -> {:ok, condition} + {:error, msg} -> + {:error, msg} + + {:ok, condition} -> + {:ok, condition} end _ -> @@ -136,12 +140,37 @@ defmodule ArkeServer.Utils.QueryFilters do defp get_condition(conn, parameter_id, operator, value, negate) do project = conn.assigns[:arke_project] - case Arke.Boundary.ParameterManager.get(parameter_id, project) do - {:error, msg} -> - {:error, msg} + {parameter_id, path_ids} = + parameter_id + |> String.split(".") + |> List.pop_at(-1) - parameter -> - {:ok, QueryManager.condition(parameter, operator, parse_value(value, operator), negate)} + with {:ok, parameter} <- fetch_parameter(parameter_id, project), + {:ok, path} <- get_path_parameters(path_ids, project) do + {:ok, + QueryManager.condition(parameter, operator, parse_value(value, operator), negate, path)} + else + {:error, msg} -> {:error, msg} + end + end + + defp get_path_parameters(path_ids, project) do + Enum.reduce_while(path_ids, {:ok, []}, fn path_id, {:ok, acc} -> + case fetch_parameter(path_id, project) do + {:ok, parameter} -> {:cont, {:ok, [parameter | acc]}} + {:error, msg} -> {:halt, {:error, msg}} + end + end) + |> case do + {:ok, path} -> {:ok, Enum.reverse(path)} + error -> error + end + end + + defp fetch_parameter(parameter_id, project) do + case Arke.Boundary.ParameterManager.get(parameter_id, project) do + {:error, msg} -> {:error, msg} + parameter -> {:ok, parameter} end end @@ -183,5 +212,6 @@ defmodule ArkeServer.Utils.QueryFilters do defp get_operator("in(" <> _rest), do: {:ok, :in} defp get_operator("isnull(" <> _rest), do: {:ok, :isnull} - defp get_operator(invalid_filter), do: Error.create(:filter, "filter `#{invalid_filter}` not available") + defp get_operator(invalid_filter), + do: Error.create(:filter, "filter `#{invalid_filter}` not available") end diff --git a/lib/arke_server/utils/query_order.ex b/lib/arke_server/utils/query_order.ex index 68150ca..74dde65 100644 --- a/lib/arke_server/utils/query_order.ex +++ b/lib/arke_server/utils/query_order.ex @@ -19,9 +19,13 @@ defmodule ArkeServer.Utils.QueryOrder do def apply_order(query, [current | tail]) do case String.split(current, ";") do - [parameter, direction] -> + [parameter_id, direction] -> + parameters = + parameter_id + |> String.split(".") + apply_order( - QueryManager.order(query, parameter, String.to_existing_atom(direction)), + QueryManager.order(query, parameters, String.to_existing_atom(direction)), tail ) diff --git a/mix.exs b/mix.exs index b377eb2..40f3c6b 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule ArkeServer.MixProject do use Mix.Project - @version "0.3.18" + @version "0.4.0-bulk.0" @scm_url "https://github.com/arkemishub/arke-server" @site_url "https://arkehub.com" @@ -69,9 +69,9 @@ defmodule ArkeServer.MixProject do {:ymlr, "~> 2.0", only: :dev}, {:excoveralls, "~> 0.10", only: :test}, {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, - {:arke, "~> 0.3.16"}, - {:arke_postgres, "~> 0.3.7"}, - {:arke_auth, "~> 0.3.5"}, + {:arke, "~> 0.4.0-bulk.0"}, + {:arke_postgres, "~> 0.4.0-bulk.0"}, + {:arke_auth, "~> 0.4.0-bulk.0"}, {:hackney, "~> 1.18"}, {:swoosh, "~> 1.11"} ])