Commit 0704a77
Changed files (3)
test
bin/idp
@@ -99,8 +99,20 @@ module Authn
end
end
- def find_by_username(username)
+ def find_by
all.find do |user|
+ yield user
+ end
+ end
+
+ def find_by_email(email)
+ find_by do |user|
+ user[:email] == email
+ end
+ end
+
+ def find_by_username(username)
+ find_by do |user|
user[:username] == username
end
end
@@ -443,7 +455,7 @@ module Authz
client_credentials_grant(params)
when 'password'
password_grant(params[:username], params[:password])
- when 'urn:ietf:params:oauth:grant-type:saml2-bearer' # RFC7522
+ when "urn:ietf:params:oauth:grant-type:saml2-bearer" # RFC-7522
saml_assertion_grant(params[:assertion])
when 'urn:ietf:params:oauth:grant-type:jwt-bearer' # RFC7523
jwt_bearer_grant(params)
@@ -469,8 +481,19 @@ module Authz
raise NotImplementedError
end
- def saml_assertion_grant(saml_assertion)
- raise NotImplementedError
+ def saml_assertion_grant(encoded_saml_assertion)
+ xml = Base64.decode64(encoded_saml_assertion)
+ saml_response = Saml::Kit::Document.to_saml_document(xml)
+ saml_assertion = saml_response.assertion
+ # TODO:: Validate signature and prevent assertion reuse
+
+ user = case saml_assertion.name_id_format
+ when Saml::Kit::Namespaces::EMAIL_ADDRESS
+ ::Authn::User.find_by_email(saml_assertion.name_id)
+ when Saml::Kit::Namespaces::PERSISTENT
+ ::Authn::User.find(saml_assertion.name_id)
+ end
+ new(user, saml_assertion: saml_assertion)
end
def jwt_bearer_grant(params)
@@ -517,7 +540,7 @@ module Authz
expires_in: 3600,
refresh_token: SecureRandom.hex(32)
}.tap do |body|
- if params['scope'].include?("openid")
+ if params["scope"]&.include?("openid")
body[:id_token] = user.create_id_token.to_jwt
end
end
bin/ui
@@ -388,6 +388,7 @@ class UI
<body style="background-color: pink;">
<h2>Received SAML Response</h2>
<textarea readonly="readonly" disabled="disabled" cols=220 rows=40><%=- saml_response.to_xml(pretty: true) -%></textarea>
+ <pre id="saml-response"><%= request.params["SAMLResponse"] %></pre>
</body>
</html>
ERB
test/e2e_test.go
@@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
+ "encoding/base64"
"net/http"
"net/url"
"strings"
@@ -58,6 +59,20 @@ func TestAuthx(t *testing.T) {
assert.Equal(t, "http://ui.example.com:8080/saml/assertions", action)
assert.NoError(t, page.Locator("#submit-button").Click())
assert.Contains(t, x.Must(page.Content()), "Received SAML Response")
+
+ t.Run("exchange SAML assertion for access token", func(t *testing.T) {
+ samlAssertion := x.Must(page.Locator("#saml-response").TextContent())
+ io := bytes.NewBuffer(nil)
+ assert.NoError(t, serde.ToJSON(io, map[string]string{
+ "assertion": samlAssertion,
+ "grant_type": "urn:ietf:params:oauth:grant-type:saml2-bearer",
+ }))
+ request := x.Must(http.NewRequestWithContext(t.Context(), "POST", "http://idp.example.com:8080/oauth/token", io))
+ request.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("client_id:client_secret")))
+ request.Header.Add("Content-Type", "application/json ")
+ response := x.Must(client.Do(request))
+ require.Equal(t, http.StatusOK, response.StatusCode)
+ })
})
})