Skip to content

Commit

Permalink
Merge pull request #182 from resgateio/bugfix/gh-181-access-control-a…
Browse files Browse the repository at this point in the history
…llow-credentials-header

Bugfix/gh 181 access control allow credentials header
  • Loading branch information
jirenius committed Feb 3, 2021
2 parents 07a0f9a + 08366df commit 7cd3202
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 14 deletions.
3 changes: 3 additions & 0 deletions server/apiHandler.go
Expand Up @@ -30,6 +30,9 @@ func (s *Service) initAPIHandler() error {
// setCommonHeaders sets common headers such as Access-Control-*.
// It returns error if the origin header does not match any allowed origin.
func (s *Service) setCommonHeaders(w http.ResponseWriter, r *http.Request) error {
if s.cfg.HeaderAuth != nil {
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
if s.cfg.allowOrigin[0] == "*" {
w.Header().Set("Access-Control-Allow-Origin", "*")
return nil
Expand Down
4 changes: 1 addition & 3 deletions server/wsConn.go
Expand Up @@ -49,10 +49,8 @@ func (s *Service) newWSConn(ws *websocket.Conn, request *http.Request, protocol
return nil
}

cid := xid.New()

conn := &wsConn{
cid: cid.String(),
cid: xid.New().String(),
ws: ws,
request: request,
serv: s,
Expand Down
87 changes: 83 additions & 4 deletions test/14http_get_test.go
Expand Up @@ -257,11 +257,11 @@ func TestHTTPGet_AllowOrigin_ExpectedResponse(t *testing.T) {
ExpectedMissingHeaders []string // Expected response headers not to be included
ExpectedBody interface{} // Expected response body
}{
{"http://localhost", "", "*", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "*"}, []string{"Vary"}, successResponse},
{"http://localhost", "", "http://localhost", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, nil, successResponse},
{"https://resgate.io", "", "http://localhost;https://resgate.io", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "https://resgate.io", "Vary": "Origin"}, nil, successResponse},
{"http://localhost", "", "*", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "*"}, []string{"Vary", "Access-Control-Allow-Credentials"}, successResponse},
{"http://localhost", "", "http://localhost", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, []string{"Access-Control-Allow-Credentials"}, successResponse},
{"https://resgate.io", "", "http://localhost;https://resgate.io", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "https://resgate.io", "Vary": "Origin"}, []string{"Access-Control-Allow-Credentials"}, successResponse},
// Invalid requests
{"http://example.com", "", "http://localhost;https://resgate.io", http.StatusForbidden, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, nil, reserr.ErrForbiddenOrigin},
{"http://example.com", "", "http://localhost;https://resgate.io", http.StatusForbidden, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, []string{"Access-Control-Allow-Credentials"}, reserr.ErrForbiddenOrigin},
// No Origin header in request
{"", "", "*", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "*"}, []string{"Vary"}, successResponse},
{"", "", "http://localhost", http.StatusOK, nil, []string{"Access-Control-Allow-Origin", "Vary"}, successResponse},
Expand Down Expand Up @@ -300,3 +300,82 @@ func TestHTTPGet_AllowOrigin_ExpectedResponse(t *testing.T) {
})
}
}

func TestHTTPGet_HeaderAuth_ExpectedResponse(t *testing.T) {
model := resourceData("test.model")
token := json.RawMessage(`{"user":"foo"}`)
successResponse := json.RawMessage(model)

tbl := []struct {
AuthResponse interface{} // Response on auth request. requestTimeout means timeout.
Token interface{} // Token to send. noToken means no token events should be sent.
ExpectedHeaders map[string]string // Expected response Headers
}{
// Without token
{requestTimeout, noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{reserr.ErrNotFound, noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{[]byte(`{]`), noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{nil, noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
// With token
{requestTimeout, token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{reserr.ErrNotFound, token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{[]byte(`{]`), token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{nil, token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
// With nil token
{requestTimeout, nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{reserr.ErrNotFound, nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{[]byte(`{]`), nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{nil, nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
}

for i, l := range tbl {
l := l
runNamedTest(t, fmt.Sprintf("#%d", i+1), func(s *Session) {
hreq := s.HTTPRequest("GET", "/api/test/model", nil, func(req *http.Request) {
req.Header.Set("Origin", "example.com")
})

req := s.GetRequest(t)
req.AssertSubject(t, "auth.vault.method")
req.AssertPathPayload(t, "header.Origin", []string{"example.com"})
// Send token
expectedToken := l.Token
if l.Token != noToken {
cid := req.PathPayload(t, "cid").(string)
s.ConnEvent(cid, "token", struct {
Token interface{} `json:"token"`
}{l.Token})
} else {
expectedToken = nil
}
// Respond to auth request
if l.AuthResponse == requestTimeout {
req.Timeout()
} else if err, ok := l.AuthResponse.(*reserr.Error); ok {
req.RespondError(err)
} else if raw, ok := l.AuthResponse.([]byte); ok {
req.RespondRaw(raw)
} else {
req.RespondSuccess(l.AuthResponse)
}

// Handle model get and access request
mreqs := s.GetParallelRequests(t, 2)
mreqs.
GetRequest(t, "access.test.model").
AssertPathPayload(t, "token", expectedToken).
RespondSuccess(json.RawMessage(`{"get":true}`))
mreqs.
GetRequest(t, "get.test.model").
RespondSuccess(json.RawMessage(`{"model":` + model + `}`))

// Validate http response
hreq.GetResponse(t).
Equals(t, http.StatusOK, successResponse).
AssertHeaders(t, l.ExpectedHeaders)
}, func(cfg *server.Config) {
headerAuth := "vault.method"
cfg.HeaderAuth = &headerAuth
})
}
}
85 changes: 81 additions & 4 deletions test/15http_post_test.go
Expand Up @@ -268,11 +268,11 @@ func TestHTTPPost_AllowOrigin_ExpectedResponse(t *testing.T) {
ExpectedMissingHeaders []string // Expected response headers not to be included
ExpectedBody interface{} // Expected response body
}{
{"http://localhost", "", "*", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "*"}, []string{"Vary"}, successResponse},
{"http://localhost", "", "http://localhost", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, nil, successResponse},
{"https://resgate.io", "", "http://localhost;https://resgate.io", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "https://resgate.io", "Vary": "Origin"}, nil, successResponse},
{"http://localhost", "", "*", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "*"}, []string{"Vary", "Access-Control-Allow-Credentials"}, successResponse},
{"http://localhost", "", "http://localhost", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, []string{"Access-Control-Allow-Credentials"}, successResponse},
{"https://resgate.io", "", "http://localhost;https://resgate.io", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "https://resgate.io", "Vary": "Origin"}, []string{"Access-Control-Allow-Credentials"}, successResponse},
// Invalid requests
{"http://example.com", "", "http://localhost;https://resgate.io", http.StatusForbidden, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, nil, reserr.ErrForbiddenOrigin},
{"http://example.com", "", "http://localhost;https://resgate.io", http.StatusForbidden, map[string]string{"Access-Control-Allow-Origin": "http://localhost", "Vary": "Origin"}, []string{"Access-Control-Allow-Credentials"}, reserr.ErrForbiddenOrigin},
// No Origin header in request
{"", "", "*", http.StatusOK, map[string]string{"Access-Control-Allow-Origin": "*"}, []string{"Vary"}, successResponse},
{"", "", "http://localhost", http.StatusOK, nil, []string{"Access-Control-Allow-Origin", "Vary"}, successResponse},
Expand Down Expand Up @@ -311,3 +311,80 @@ func TestHTTPPost_AllowOrigin_ExpectedResponse(t *testing.T) {
})
}
}

func TestHTTPPost_HeaderAuth_ExpectedResponse(t *testing.T) {
token := json.RawMessage(`{"user":"foo"}`)
successResponse := json.RawMessage(`{"foo":"bar"}`)

tbl := []struct {
AuthResponse interface{} // Response on auth request. requestTimeout means timeout.
Token interface{} // Token to send. noToken means no token events should be sent.
ExpectedHeaders map[string]string // Expected response Headers
}{
// Without token
{requestTimeout, noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{reserr.ErrNotFound, noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{[]byte(`{]`), noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{nil, noToken, map[string]string{"Access-Control-Allow-Credentials": "true"}},
// With token
{requestTimeout, token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{reserr.ErrNotFound, token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{[]byte(`{]`), token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{nil, token, map[string]string{"Access-Control-Allow-Credentials": "true"}},
// With nil token
{requestTimeout, nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{reserr.ErrNotFound, nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{[]byte(`{]`), nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
{nil, nil, map[string]string{"Access-Control-Allow-Credentials": "true"}},
}

for i, l := range tbl {
l := l
runNamedTest(t, fmt.Sprintf("#%d", i+1), func(s *Session) {
hreq := s.HTTPRequest("POST", "/api/test/model/method", nil, func(req *http.Request) {
req.Header.Set("Origin", "example.com")
})

req := s.GetRequest(t)
req.AssertSubject(t, "auth.vault.method")
req.AssertPathPayload(t, "header.Origin", []string{"example.com"})
// Send token
expectedToken := l.Token
if l.Token != noToken {
cid := req.PathPayload(t, "cid").(string)
s.ConnEvent(cid, "token", struct {
Token interface{} `json:"token"`
}{l.Token})
} else {
expectedToken = nil
}
// Respond to auth request
if l.AuthResponse == requestTimeout {
req.Timeout()
} else if err, ok := l.AuthResponse.(*reserr.Error); ok {
req.RespondError(err)
} else if raw, ok := l.AuthResponse.([]byte); ok {
req.RespondRaw(raw)
} else {
req.RespondSuccess(l.AuthResponse)
}

// Handle model get and access request
s.GetRequest(t).
AssertSubject(t, "access.test.model").
AssertPathPayload(t, "token", expectedToken).
RespondSuccess(json.RawMessage(`{"get":true,"call":"*"}`))
s.GetRequest(t).
AssertSubject(t, "call.test.model.method").
RespondSuccess(successResponse)

// Validate http response
hreq.GetResponse(t).
Equals(t, http.StatusOK, successResponse).
AssertHeaders(t, l.ExpectedHeaders)
}, func(cfg *server.Config) {
headerAuth := "vault.method"
cfg.HeaderAuth = &headerAuth
})
}
}
20 changes: 17 additions & 3 deletions test/21http_options_test.go
Expand Up @@ -49,9 +49,9 @@ func TestHTTPOptions_RequestHeaders_ExpectedResponseHeaders(t *testing.T) {
ExpectedHeaders map[string]string // Expected response Headers
ExpectedMissingHeaders []string // Expected response headers not to be included
}{
{[]string{"Content-Type"}, map[string]string{"Access-Control-Allow-Headers": "Content-Type"}, nil},
{[]string{"X-PINGOTHER", "Content-Type"}, map[string]string{"Access-Control-Allow-Headers": "X-PINGOTHER, Content-Type"}, nil},
{[]string{"X-PINGOTHER", "Content-Type", "Authorization"}, map[string]string{"Access-Control-Allow-Headers": "X-PINGOTHER, Content-Type, Authorization"}, nil},
{[]string{"Content-Type"}, map[string]string{"Access-Control-Allow-Headers": "Content-Type"}, []string{"Access-Control-Allow-Credentials"}},
{[]string{"X-PINGOTHER", "Content-Type"}, map[string]string{"Access-Control-Allow-Headers": "X-PINGOTHER, Content-Type"}, []string{"Access-Control-Allow-Credentials"}},
{[]string{"X-PINGOTHER", "Content-Type", "Authorization"}, map[string]string{"Access-Control-Allow-Headers": "X-PINGOTHER, Content-Type, Authorization"}, []string{"Access-Control-Allow-Credentials"}},
{nil, nil, []string{"Access-Control-Allow-Headers"}},
}

Expand All @@ -71,3 +71,17 @@ func TestHTTPOptions_RequestHeaders_ExpectedResponseHeaders(t *testing.T) {
})
}
}

func TestHTTPOptions_HeaderAuth_HasExpectedResponseHeaders(t *testing.T) {

runTest(t, func(s *Session) {
hreq := s.HTTPRequest("OPTIONS", "/api/test/model", nil)
// Validate http response
hreq.GetResponse(t).
Equals(t, http.StatusOK, nil).
AssertHeaders(t, map[string]string{"Access-Control-Allow-Credentials": "true"})
}, func(cfg *server.Config) {
headerAuth := "vault.method"
cfg.HeaderAuth = &headerAuth
})
}
1 change: 1 addition & 0 deletions test/resources.go
Expand Up @@ -104,6 +104,7 @@ var resources = map[string]resource{
const (
requestTimeout uint64 = iota
noRequest
noToken
)

type sequenceEvent struct {
Expand Down

0 comments on commit 7cd3202

Please sign in to comment.