fix(op): omit empty state from code flow redirect (#428)
* chore(op): reproduce issue #415 * fix(op): omit empty state from code flow redirect Add test cases to reproduce the original bug, and it's resolution. closes #415
This commit is contained in:
parent
45582b6ee9
commit
37b5de0e82
2 changed files with 134 additions and 5 deletions
|
@ -448,11 +448,11 @@ func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthReques
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
codeResponse := struct {
|
codeResponse := struct {
|
||||||
code string
|
Code string `schema:"code"`
|
||||||
state string
|
State string `schema:"state,omitempty"`
|
||||||
}{
|
}{
|
||||||
code: code,
|
Code: code,
|
||||||
state: authReq.GetState(),
|
State: authReq.GetState(),
|
||||||
}
|
}
|
||||||
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 {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package op_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -13,7 +14,7 @@ import (
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zitadel/oidc/v2/example/server/storage"
|
||||||
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
httphelper "github.com/zitadel/oidc/v2/pkg/http"
|
||||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||||
"github.com/zitadel/oidc/v2/pkg/op"
|
"github.com/zitadel/oidc/v2/pkg/op"
|
||||||
|
@ -942,3 +943,131 @@ func (m *mockEncoder) Encode(src interface{}, dst map[string][]string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockCrypto implements the op.Crypto interface
|
||||||
|
// and in always equals out. (It doesn't crypt anything).
|
||||||
|
// When returnErr != nil, that error is always returned instread.
|
||||||
|
type mockCrypto struct {
|
||||||
|
returnErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCrypto) Encrypt(s string) (string, error) {
|
||||||
|
if c.returnErr != nil {
|
||||||
|
return "", c.returnErr
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCrypto) Decrypt(s string) (string, error) {
|
||||||
|
if c.returnErr != nil {
|
||||||
|
return "", c.returnErr
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthResponseCode(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
authReq op.AuthRequest
|
||||||
|
authorizer func(*testing.T) op.Authorizer
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
wantCode int
|
||||||
|
wantLocationHeader string
|
||||||
|
wantBody string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "create code error",
|
||||||
|
args: args{
|
||||||
|
authReq: &storage.AuthRequest{
|
||||||
|
ID: "id1",
|
||||||
|
TransferState: "state1",
|
||||||
|
},
|
||||||
|
authorizer: func(t *testing.T) op.Authorizer {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
storage := mock.NewMockStorage(ctrl)
|
||||||
|
|
||||||
|
authorizer := mock.NewMockAuthorizer(ctrl)
|
||||||
|
authorizer.EXPECT().Storage().Return(storage)
|
||||||
|
authorizer.EXPECT().Crypto().Return(&mockCrypto{
|
||||||
|
returnErr: io.ErrClosedPipe,
|
||||||
|
})
|
||||||
|
authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
|
||||||
|
return authorizer
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantCode: http.StatusBadRequest,
|
||||||
|
wantBody: "io: read/write on closed pipe\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success with state",
|
||||||
|
args: args{
|
||||||
|
authReq: &storage.AuthRequest{
|
||||||
|
ID: "id1",
|
||||||
|
TransferState: "state1",
|
||||||
|
},
|
||||||
|
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.StatusFound,
|
||||||
|
wantLocationHeader: "/auth/callback/?code=id1&state=state1",
|
||||||
|
wantBody: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success without state", // reproduce issue #415
|
||||||
|
args: args{
|
||||||
|
authReq: &storage.AuthRequest{
|
||||||
|
ID: "id1",
|
||||||
|
TransferState: "",
|
||||||
|
},
|
||||||
|
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.StatusFound,
|
||||||
|
wantLocationHeader: "/auth/callback/?code=id1",
|
||||||
|
wantBody: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/auth/callback/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
op.AuthResponseCode(w, r, tt.args.authReq, tt.args.authorizer(t))
|
||||||
|
resp := w.Result()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
assert.Equal(t, tt.res.wantCode, resp.StatusCode)
|
||||||
|
assert.Equal(t, tt.res.wantLocationHeader, resp.Header.Get("Location"))
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.res.wantBody, string(body))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue