From 2e20c1ca475c218e752c590b6ae9a227219acb1e Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Sun, 30 Oct 2022 02:45:29 +0800 Subject: [PATCH 1/4] add cas auth provider strategy --- lib/cadet/auth/providers/cas.ex | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 lib/cadet/auth/providers/cas.ex diff --git a/lib/cadet/auth/providers/cas.ex b/lib/cadet/auth/providers/cas.ex new file mode 100644 index 000000000..b47756b6e --- /dev/null +++ b/lib/cadet/auth/providers/cas.ex @@ -0,0 +1,43 @@ +defmodule Cadet.Auth.Providers.CAS do + @moduledoc """ + Provides identity using CAS Protocol. + https://apereo.github.io/cas/6.5.x/protocol/CAS-Protocol.html + """ + + alias Cadet.Auth.Provider + + @behaviour Provider + + @type config :: %{service_validate_endpoint: String.t(), modules: %{}} + + @spec authorise(config(), Provider.code(), Provider.client_id(), Provider.redirect_uri()) :: + {:ok, %{token: Provider.token(), username: String.t()}} + | {:error, Provider.error(), String.t()} + def authorise(config, code, _client_id, redirect_uri) do + params = %{ + ticket: code, + service: redirect_uri + } + + with {:validate, {:ok, %{body: body, status_code: 200}}} <- + {:validate, HTTPoison.get(config.service_validate_endpoint, [], params: params)}, + {:validation_response, data} <- {:validation_response, Jason.decode!(body)}, + {:extract_username, %{"name" => username}} <- {:extract_username, data} do + IO.inspect(data) + {:ok, %{token: data, username: username}} + else + {:validate, {:ok, %{body: body, status_code: status}}} -> + {:error, :upstream, "Status code #{status} from CAS: #{body}"} + end + end + + @spec get_name(config(), Provider.token()) :: + {:ok, String.t()} | {:error, Provider.error(), String.t()} + def get_name(_config, token) do + %{"name" => name} = token + {:ok, name} + rescue + _ -> + {:error, :invalid_credentials, "Failed to retrieve user's name"} + end +end From 156fbb0c02affd05bc09708e4c9407e18ca66f16 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Sun, 30 Oct 2022 03:59:29 +0800 Subject: [PATCH 2/4] printing out cas user information returned from /serviceValidate --- lib/cadet/auth/providers/cas.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/cadet/auth/providers/cas.ex b/lib/cadet/auth/providers/cas.ex index b47756b6e..d1ae9d886 100644 --- a/lib/cadet/auth/providers/cas.ex +++ b/lib/cadet/auth/providers/cas.ex @@ -20,11 +20,11 @@ defmodule Cadet.Auth.Providers.CAS do } with {:validate, {:ok, %{body: body, status_code: 200}}} <- - {:validate, HTTPoison.get(config.service_validate_endpoint, [], params: params)}, - {:validation_response, data} <- {:validation_response, Jason.decode!(body)}, - {:extract_username, %{"name" => username}} <- {:extract_username, data} do - IO.inspect(data) - {:ok, %{token: data, username: username}} + {:validate, HTTPoison.get(config.service_validate_endpoint, [], params: params)} do + # {:validation_response, data} <- {:validation_response, Jason.decode!(body)}, + # {:extract_username, %{"name" => username}} <- {:extract_username, data} do + IO.inspect(body) + {:ok, %{token: body, username: "placeholder"}} else {:validate, {:ok, %{body: body, status_code: status}}} -> {:error, :upstream, "Status code #{status} from CAS: #{body}"} From 9eef5f25c586d3dd7bc414f0522fb4f1814d474e Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:22:03 +0800 Subject: [PATCH 3/4] fix handling of xml on successful validation with CAS server and update cadet.exs.example to include example CAS strategy --- config/cadet.exs.example | 5 +++++ lib/cadet/auth/providers/cas.ex | 27 +++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/config/cadet.exs.example b/config/cadet.exs.example index 1040e3c0e..e5f48496c 100644 --- a/config/cadet.exs.example +++ b/config/cadet.exs.example @@ -56,6 +56,11 @@ config :cadet, # # You may need to write your own claim extractor for other providers # claim_extractor: Cadet.Auth.Providers.CognitoClaimExtractor # }}, + # # To use authentication with CAS + # "cas" => + # {Cadet.Auth.Providers.CAS, + # %{service_validate_endpoint: "https://{CAS_SERVER_ENDPOINT}/serviceValidate"}}, + "test" => {Cadet.Auth.Providers.Config, [ diff --git a/lib/cadet/auth/providers/cas.ex b/lib/cadet/auth/providers/cas.ex index d1ae9d886..0c3e7ae13 100644 --- a/lib/cadet/auth/providers/cas.ex +++ b/lib/cadet/auth/providers/cas.ex @@ -4,6 +4,8 @@ defmodule Cadet.Auth.Providers.CAS do https://apereo.github.io/cas/6.5.x/protocol/CAS-Protocol.html """ + import SweetXml + alias Cadet.Auth.Provider @behaviour Provider @@ -20,24 +22,37 @@ defmodule Cadet.Auth.Providers.CAS do } with {:validate, {:ok, %{body: body, status_code: 200}}} <- - {:validate, HTTPoison.get(config.service_validate_endpoint, [], params: params)} do - # {:validation_response, data} <- {:validation_response, Jason.decode!(body)}, - # {:extract_username, %{"name" => username}} <- {:extract_username, data} do - IO.inspect(body) - {:ok, %{token: body, username: "placeholder"}} + {:validate, HTTPoison.get(config.service_validate_endpoint, [], params: params)}, + {:authentication_success, success_xml} when not is_nil(success_xml) <- + {:authentication_success, authentication_success(body)}, + {:extract_username, username} <- {:extract_username, get_username(success_xml)} do + {:ok, %{token: success_xml, username: username}} else {:validate, {:ok, %{body: body, status_code: status}}} -> {:error, :upstream, "Status code #{status} from CAS: #{body}"} + + {:authentication_success, nil} -> + {:error, :upstream, "Authentication failure from CAS"} end end @spec get_name(config(), Provider.token()) :: {:ok, String.t()} | {:error, Provider.error(), String.t()} def get_name(_config, token) do - %{"name" => name} = token + name = get_username(token) {:ok, name} rescue _ -> {:error, :invalid_credentials, "Failed to retrieve user's name"} end + + defp authentication_success(xml) do + xml + |> xpath(~x"//cas:serviceResponse/cas:authenticationSuccess"e) + end + + defp get_username(xml) do + xml + |> xpath(~x"//cas:user/text()"s) + end end From 718a00f50b40c6903d3ca5c899790e3c4566815f Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:28:55 +0800 Subject: [PATCH 4/4] Fix errors post-merge --- lib/cadet/auth/providers/cas.ex | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/cadet/auth/providers/cas.ex b/lib/cadet/auth/providers/cas.ex index 0c3e7ae13..ecfc33902 100644 --- a/lib/cadet/auth/providers/cas.ex +++ b/lib/cadet/auth/providers/cas.ex @@ -12,10 +12,13 @@ defmodule Cadet.Auth.Providers.CAS do @type config :: %{service_validate_endpoint: String.t(), modules: %{}} - @spec authorise(config(), Provider.code(), Provider.client_id(), Provider.redirect_uri()) :: + @spec authorise(config(), Provider.authorise_params()) :: {:ok, %{token: Provider.token(), username: String.t()}} | {:error, Provider.error(), String.t()} - def authorise(config, code, _client_id, redirect_uri) do + def authorise(config, %{ + code: code, + redirect_uri: redirect_uri + }) do params = %{ ticket: code, service: redirect_uri