Skip to content

Commit

Permalink
feat: Add PostBinding method on IDP authn. requests (#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
hf committed Jan 12, 2023
1 parent f892c9f commit 9e43d2e
Showing 1 changed file with 52 additions and 38 deletions.
90 changes: 52 additions & 38 deletions identity_provider.go
Expand Up @@ -875,58 +875,72 @@ func (req *IdpAuthnRequest) MakeAssertionEl() error {
return nil
}

// WriteResponse writes the `Response` to the http.ResponseWriter. If
// `Response` is not already set, it calls MakeResponse to produce it.
func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error {
// IdpAuthnRequestForm contans HTML form information to be submitted to the
// SAML HTTP POST binding ACS.
type IdpAuthnRequestForm struct {
URL string
SAMLResponse string
RelayState string
}

// PostBinding creates the HTTP POST form information for this
// `IdpAuthnRequest`. If `Response` is not already set, it calls MakeResponse
// to produce it.
func (req *IdpAuthnRequest) PostBinding() (IdpAuthnRequestForm, error) {
var form IdpAuthnRequestForm

if req.ResponseEl == nil {
if err := req.MakeResponse(); err != nil {
return err
return form, err
}
}

doc := etree.NewDocument()
doc.SetRoot(req.ResponseEl)
responseBuf, err := doc.WriteToBytes()
if err != nil {
return err
return form, err
}

// the only supported binding is the HTTP-POST binding
switch req.ACSEndpoint.Binding {
case HTTPPostBinding:
tmpl := template.Must(template.New("saml-post-form").Parse(`<html>` +
`<form method="post" action="{{.URL}}" id="SAMLResponseForm">` +
`<input type="hidden" name="SAMLResponse" value="{{.SAMLResponse}}" />` +
`<input type="hidden" name="RelayState" value="{{.RelayState}}" />` +
`<input id="SAMLSubmitButton" type="submit" value="Continue" />` +
`</form>` +
`<script>document.getElementById('SAMLSubmitButton').style.visibility='hidden';</script>` +
`<script>document.getElementById('SAMLResponseForm').submit();</script>` +
`</html>`))
data := struct {
URL string
SAMLResponse string
RelayState string
}{
URL: req.ACSEndpoint.Location,
SAMLResponse: base64.StdEncoding.EncodeToString(responseBuf),
RelayState: req.RelayState,
}

buf := bytes.NewBuffer(nil)
if err := tmpl.Execute(buf, data); err != nil {
return err
}
if _, err := io.Copy(w, buf); err != nil {
return err
}
return nil

default:
return fmt.Errorf("%s: unsupported binding %s",
if req.ACSEndpoint.Binding != HTTPPostBinding {
return form, fmt.Errorf("%s: unsupported binding %s",
req.ServiceProviderMetadata.EntityID,
req.ACSEndpoint.Binding)
}

form.URL = req.ACSEndpoint.Location
form.SAMLResponse = base64.StdEncoding.EncodeToString(responseBuf)
form.RelayState = req.RelayState

return form, nil
}

// WriteResponse writes the `Response` to the http.ResponseWriter. If
// `Response` is not already set, it calls MakeResponse to produce it.
func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error {
form, err := req.PostBinding()
if err != nil {
return err
}

tmpl := template.Must(template.New("saml-post-form").Parse(`<html>` +
`<form method="post" action="{{.URL}}" id="SAMLResponseForm">` +
`<input type="hidden" name="SAMLResponse" value="{{.SAMLResponse}}" />` +
`<input type="hidden" name="RelayState" value="{{.RelayState}}" />` +
`<input id="SAMLSubmitButton" type="submit" value="Continue" />` +
`</form>` +
`<script>document.getElementById('SAMLSubmitButton').style.visibility='hidden';</script>` +
`<script>document.getElementById('SAMLResponseForm').submit();</script>` +
`</html>`))

buf := bytes.NewBuffer(nil)
if err := tmpl.Execute(buf, form); err != nil {
return err
}
if _, err := io.Copy(w, buf); err != nil {
return err
}
return nil
}

// getSPEncryptionCert returns the certificate which we can use to encrypt things
Expand Down

0 comments on commit 9e43d2e

Please sign in to comment.