Skip to content

Commit 0322810

Browse files
committed
v1.0.0-rc.1
1 parent 0605bdf commit 0322810

10 files changed

Lines changed: 49 additions & 25 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# CHANGELOG
22

3+
### v1.0.0-rc.1
4+
5+
+ `target_url` query parameter for the sign-in/sign-out requests must be
6+
`x-www-form-urlencoded`.
7+
8+
+ Redirect URLs are properly encoded.
9+
10+
+ Switched to `report-to` in content security policy.
11+
12+
+ `cache-control` header value updated.
13+
314
### v1.0.0-rc.0
415

516
+ Issue: #33 - Content Security Policy

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ plug enabled routes.
1515
defp deps() do
1616
[
1717
# ...
18-
{:samly, "~> 1.0.0-rc.0"},
19-
# v1.0.0-rc.0 uses esaml v4.2 which in turn relies on cowboy 2.x
18+
{:samly, "~> 1.0.0-rc.1"},
19+
# v1.0.0-rc.1 uses esaml v4.2 which in turn relies on cowboy 2.x
2020
# If you need to work with cowboy 1.x, you need the following override:
2121
# {:esaml, "~> 3.7", override: true}
2222
]
@@ -199,8 +199,8 @@ config :samly, Samly.Provider,
199199
| `sign_requests`, `sign_metadata` | _(optional)_ Default is `true`. |
200200
| `signed_assertion_in_resp`, `signed_envelopes_in_resp` | _(optional)_ Default is `true`. When `true`, `Samly` expects the requests and responses from IdP to be signed. |
201201
| `allow_idp_initiated_flow` | _(optional)_ Default is `false`. IDP initiated SSO is allowed only when this is set to `true`. |
202-
| `allowed_target_urls` | _(optional)_ Default is `[]`. `Samly` uses this **only** when `allow_idp_initiated_flow` parameter is set to `true`. Make sure to set this to one or more exact URLs you want to allow (whitelist). The URL to redirect the user after completing the SSO flow is sent from IDP in auth response as `relay_state`. This `relay_state` target URL is matched against this URL list. Set the value to `nil` if you do not want this whitelist capability. |
203-
| `nameid_format` | _(optional)_ When this is specified, `Samly` will put the value in the `Format` attribute of the `NameIDPolicy` element of the login request. Value may be a string, a character list, or one of the following atoms: `:email`, `:x509`, `:windows`, `:krb`, `:persistent`, `:transient`. |
202+
| `allowed_target_urls` | _(optional)_ Default is `[]`. `Samly` uses this **only** when `allow_idp_initiated_flow` parameter is set to `true`. Make sure to set this to one or more exact URLs you want to allow (whitelist). The URL to redirect the user after completing the SSO flow is sent from IDP in auth response as `relay_state`. This `relay_state` target URL is matched against this URL list. Set the value to `nil` if you do not want this whitelist capability. |
203+
| `nameid_format` | _(optional)_ When specified, `Samly` includes the value as the `NameIDPolicy` element's `Format` attribute in the login request. Value must either be a string or one of the following atoms: `:email`, `:x509`, `:windows`, `:krb`, `:persistent`, `:transient`. Use the string value when you need to specify a non-standard/custom nameid format supported by your IdP. |
204204

205205
#### Authenticated SAML Assertion State Store
206206

lib/samly/auth_handler.ex

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,12 @@ defmodule Samly.AuthHandler do
4040
def initiate_sso_req(conn) do
4141
import Plug.CSRFProtection, only: [get_csrf_token: 0]
4242

43-
target_url =
44-
case conn.params["target_url"] do
45-
nil -> nil
46-
url -> URI.decode_www_form(url)
47-
end
43+
target_url = conn.private[:samly_target_url] || "/"
4844

4945
opts = [
5046
nonce: conn.private[:samly_nonce],
51-
action: conn.request_path,
52-
target_url: target_url,
47+
action: URI.encode(conn.request_path),
48+
target_url: URI.encode_www_form(target_url),
5349
csrf_token: get_csrf_token()
5450
]
5551

@@ -63,7 +59,7 @@ defmodule Samly.AuthHandler do
6359
%IdpData{esaml_idp_rec: idp_rec, esaml_sp_rec: sp_rec} = idp
6460
sp = ensure_sp_uris_set(sp_rec, conn)
6561

66-
target_url = (conn.params["target_url"] || "/") |> URI.decode_www_form()
62+
target_url = conn.private[:samly_target_url] || "/"
6763
assertion_key = get_session(conn, "samly_assertion_key")
6864

6965
case State.get_assertion(conn, assertion_key) do
@@ -85,7 +81,7 @@ defmodule Samly.AuthHandler do
8581
idp_signin_url,
8682
idp.use_redirect_for_req,
8783
req_xml_frag,
88-
relay_state |> URI.encode_www_form()
84+
relay_state
8985
)
9086
end
9187

@@ -100,7 +96,7 @@ defmodule Samly.AuthHandler do
10096
%IdpData{esaml_idp_rec: idp_rec, esaml_sp_rec: sp_rec} = idp
10197
sp = ensure_sp_uris_set(sp_rec, conn)
10298

103-
target_url = (conn.params["target_url"] || "/") |> URI.decode_www_form()
99+
target_url = conn.private[:samly_target_url] || "/"
104100
assertion_key = get_session(conn, "samly_assertion_key")
105101

106102
case State.get_assertion(conn, assertion_key) do
@@ -123,7 +119,7 @@ defmodule Samly.AuthHandler do
123119
idp_signout_url,
124120
idp.use_redirect_for_req,
125121
req_xml_frag,
126-
relay_state |> URI.encode_www_form()
122+
relay_state
127123
)
128124

129125
_ ->

lib/samly/auth_router.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ defmodule Samly.AuthRouter do
33

44
use Plug.Router
55
import Plug.Conn
6-
import Samly.RouterUtil, only: [check_idp_id: 2]
6+
import Samly.RouterUtil, only: [check_idp_id: 2, check_target_url: 2]
77

88
plug :fetch_session
99
plug Plug.CSRFProtection
1010
plug :match
1111
plug :check_idp_id
12+
plug :check_target_url
1213
plug :dispatch
1314

1415
get "/signin/*idp_id_seg" do

lib/samly/helper.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ defmodule Samly.Helper do
7272
{:ok, assertion_rec} <- :esaml_sp.validate_assertion(xml_frag, sp) do
7373
{:ok, Assertion.from_rec(assertion_rec)}
7474
else
75+
{:error, reason} -> {:error, reason}
7576
error -> {:error, {:invalid_request, "#{inspect(error)}"}}
7677
end
7778
end

lib/samly/router.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ defmodule Samly.Router do
1919
default-src 'none';
2020
script-src 'self' 'nonce-<%= nonce %>' 'report-sample';
2121
img-src 'self' 'report-sample';
22-
report-uri /sso/csp-report;
22+
report-to /sso/csp-report;
2323
"""
2424
|> String.replace("\n", " ")
2525

@@ -30,7 +30,7 @@ defmodule Samly.Router do
3030
nonce = connection.private[:samly_nonce]
3131

3232
connection
33-
|> put_resp_header("cache-control", "no-cache")
33+
|> put_resp_header("cache-control", "no-cache, no-store, must-revalidate")
3434
|> put_resp_header("pragma", "no-cache")
3535
|> put_resp_header("x-frame-options", "SAMEORIGIN")
3636
|> put_resp_header("content-security-policy", EEx.eval_string(@csp, nonce: nonce))

lib/samly/router_util.ex

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule Samly.RouterUtil do
22
@moduledoc false
33

44
alias Plug.Conn
5+
require Logger
56
require Samly.Esaml
67
alias Samly.{Esaml, IdpData, Helper}
78

@@ -32,6 +33,20 @@ defmodule Samly.RouterUtil do
3233
end
3334
end
3435

36+
def check_target_url(conn, _opts) do
37+
try do
38+
target_url = conn.params["target_url"] && URI.decode_www_form(conn.params["target_url"])
39+
conn |> Conn.put_private(:samly_target_url, target_url)
40+
rescue
41+
ArgumentError ->
42+
Logger.error(
43+
"[Samly] target_url must be x-www-form-urlencoded: #{inspect(conn.params["target_url"])}"
44+
)
45+
46+
conn |> Conn.send_resp(400, "target_url must be x-www-form-urlencoded") |> Conn.halt()
47+
end
48+
end
49+
3550
# generate URIs using the idp_id
3651
@spec ensure_sp_uris_set(tuple, Conn.t()) :: tuple
3752
def ensure_sp_uris_set(sp, conn) do
@@ -85,7 +100,7 @@ defmodule Samly.RouterUtil do
85100

86101
def redirect(conn, status_code, dest) do
87102
conn
88-
|> Conn.put_resp_header("location", dest)
103+
|> Conn.put_resp_header("location", URI.encode(dest))
89104
|> Conn.send_resp(status_code, "")
90105
|> Conn.halt()
91106
end

lib/samly/sp_handler.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ defmodule Samly.SPHandler do
5050
conn
5151
|> configure_session(renew: true)
5252
|> put_session("samly_assertion_key", assertion_key)
53-
|> redirect(302, target_url |> URI.decode_www_form())
53+
|> redirect(302, target_url)
5454
else
5555
{:halted, conn} -> conn
5656
{:error, reason} -> conn |> send_resp(403, "access_denied #{inspect(reason)}")
@@ -133,7 +133,7 @@ defmodule Samly.SPHandler do
133133
target_url when target_url != nil <- get_session(conn, "target_url") do
134134
conn
135135
|> configure_session(drop: true)
136-
|> redirect(302, target_url |> URI.decode_www_form())
136+
|> redirect(302, target_url)
137137
else
138138
error -> conn |> send_resp(403, "invalid_request #{inspect(error)}")
139139
end
@@ -152,7 +152,7 @@ defmodule Samly.SPHandler do
152152

153153
saml_encoding = conn.body_params["SAMLEncoding"]
154154
saml_request = conn.body_params["SAMLRequest"]
155-
relay_state = conn.body_params["RelayState"]
155+
relay_state = conn.body_params["RelayState"] |> URI.decode_www_form()
156156

157157
with {:ok, payload} <- Helper.decode_idp_signout_req(sp, saml_encoding, saml_request) do
158158
Esaml.esaml_logoutreq(name: nameid, issuer: _issuer) = payload

mix.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Samly.Mixfile do
22
use Mix.Project
33

4-
@version "1.0.0-rc.0"
4+
@version "1.0.0-rc.1"
55
@description "SAML SP SSO made easy"
66
@source_url "https://github.com/handnot2/samly"
77
@blog_url "https://handnot2.github.io/blog/auth/saml-auth-for-phoenix"
@@ -31,7 +31,7 @@ defmodule Samly.Mixfile do
3131
[
3232
{:plug, "~> 1.6"},
3333
{:esaml, "~> 4.2"},
34-
{:sweet_xml, "~> 0.6"},
34+
{:sweet_xml, "~> 0.6.6"},
3535
{:ex_doc, "~> 0.19.0", only: :dev, runtime: false},
3636
{:inch_ex, "~> 1.0", only: [:dev, :test]}
3737
]

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
1414
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
1515
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
16-
"sweet_xml": {:hex, :sweet_xml, "0.6.5", "dd9cde443212b505d1b5f9758feb2000e66a14d3c449f04c572f3048c66e6697", [:mix], [], "hexpm"},
16+
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
1717
}

0 commit comments

Comments
 (0)