feat(op): Add response_mode: form_post

This commit is contained in:
Ayato 2024-02-25 02:22:26 +09:00
parent f4bbffb51b
commit ed9ed83bc8
No known key found for this signature in database
GPG key ID: 56E05AE09DBA012D
4 changed files with 99 additions and 0 deletions

View file

@ -48,6 +48,7 @@ const (
ResponseModeQuery ResponseMode = "query" ResponseModeQuery ResponseMode = "query"
ResponseModeFragment ResponseMode = "fragment" ResponseModeFragment ResponseMode = "fragment"
ResponseModeFormPost ResponseMode = "form_post"
// PromptNone (`none`) disallows the Authorization Server to display any authentication or consent user interface pages. // PromptNone (`none`) disallows the Authorization Server to display any authentication or consent user interface pages.
// An error (login_required, interaction_required, ...) will be returned if the user is not already authenticated or consent is needed // An error (login_required, interaction_required, ...) will be returned if the user is not already authenticated or consent is needed

View file

@ -2,8 +2,10 @@ package op
import ( import (
"context" "context"
_ "embed"
"errors" "errors"
"fmt" "fmt"
"html/template"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -464,6 +466,17 @@ func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthReques
Code: code, Code: code,
State: authReq.GetState(), State: authReq.GetState(),
} }
if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
err = AuthResponseFormPost(w, authReq.GetRedirectURI(), &codeResponse, authorizer.Encoder())
if err != nil {
AuthRequestError(w, r, authReq, err, authorizer)
return
}
return
}
callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), &codeResponse, authorizer.Encoder()) callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), &codeResponse, authorizer.Encoder())
if err != nil { if err != nil {
AuthRequestError(w, r, authReq, err, authorizer) AuthRequestError(w, r, authReq, err, authorizer)
@ -484,6 +497,17 @@ func AuthResponseToken(w http.ResponseWriter, r *http.Request, authReq AuthReque
AuthRequestError(w, r, authReq, err, authorizer) AuthRequestError(w, r, authReq, err, authorizer)
return return
} }
if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
err = AuthResponseFormPost(w, authReq.GetRedirectURI(), resp, authorizer.Encoder())
if err != nil {
AuthRequestError(w, r, authReq, err, authorizer)
return
}
return
}
callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), resp, authorizer.Encoder()) callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), resp, authorizer.Encoder())
if err != nil { if err != nil {
AuthRequestError(w, r, authReq, err, authorizer) AuthRequestError(w, r, authReq, err, authorizer)
@ -535,6 +559,38 @@ func AuthResponseURL(redirectURI string, responseType oidc.ResponseType, respons
return mergeQueryParams(uri, params), nil return mergeQueryParams(uri, params), nil
} }
//go:embed form_post.html.tmpl
var formPostTemplate string
// AuthResponseFormPost responds a html page that automatically submits the form which contains the auth response parameters
func AuthResponseFormPost(w http.ResponseWriter, redirectURI string, response any, encoder httphelper.Encoder) error {
t, err := template.New("form_post").Parse(formPostTemplate)
if err != nil {
return oidc.ErrServerError().WithParent(err)
}
values := make(map[string][]string)
err = encoder.Encode(response, values)
if err != nil {
return oidc.ErrServerError().WithParent(err)
}
params := &struct {
RedirectURI string
Params any
}{
RedirectURI: redirectURI,
Params: values,
}
err = t.Execute(w, params)
if err != nil {
return oidc.ErrServerError().WithParent(err)
}
return nil
}
func setFragment(uri *url.URL, params url.Values) string { func setFragment(uri *url.URL, params url.Values) string {
uri.Fragment = params.Encode() uri.Fragment = params.Encode()
return uri.String() return uri.String()

View file

@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -1111,6 +1112,33 @@ func TestAuthResponseCode(t *testing.T) {
wantBody: "", wantBody: "",
}, },
}, },
{
name: "success form_post",
args: args{
authReq: &storage.AuthRequest{
ID: "id1",
CallbackURI: "https://example.com/callback",
TransferState: "state1",
ResponseMode: "form_post",
},
authorizer: func(t *testing.T) op.Authorizer {
ctrl := gomock.NewController(t)
storage := mock.NewMockStorage(ctrl)
storage.EXPECT().SaveAuthCode(context.Background(), "id1", "id1")
authorizer := mock.NewMockAuthorizer(ctrl)
authorizer.EXPECT().Storage().Return(storage)
authorizer.EXPECT().Crypto().Return(&mockCrypto{})
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
return authorizer
},
},
res: res{
wantCode: http.StatusOK,
wantLocationHeader: "",
wantBody: "<!doctype html>\n<html>\n<head><meta charset=\"UTF-8\" /></head>\n<body onload=\"javascript:document.forms[0].submit()\">\n<form method=\"post\" action=\"https://example.com/callback\">\n<input type=\"hidden\" name=\"state\" value=\"state1\"/>\n<input type=\"hidden\" name=\"code\" value=\"id1\" />\n\n\n\n</form>\n</body>\n</html>",
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head><meta charset="UTF-8" /></head>
<body onload="javascript:document.forms[0].submit()">
<form method="post" action="{{ .RedirectURI }}">
{{with .Params.state}}<input type="hidden" name="state" value="{{ index . 0 }}"/>{{end}}
{{with .Params.code}}<input type="hidden" name="code" value="{{ index . 0 }}" />{{end}}
{{with .Params.id_token}}<input type="hidden" name="id_token" value="{{ index . 0 }}"/>{{end}}
{{with .Params.access_token}}<input type="hidden" name="access_token" value="{{ index . 0 }}" />{{end}}
{{with .Params.token_type}}<input type="hidden" name="token_type" value="{{ index . 0 }}" />{{end}}
{{with .Params.expires_in}}<input type="hidden" name="expires_in" value="{{ index . 0 }}" />{{end}}
</form>
</body>
</html>