Skip to content

Commit

Permalink
BCDA-5681: Unknown Entity - Return 403 (#776)
Browse files Browse the repository at this point in the history
* BCDA-5681: Unknown Entity - Return 403

* BCDA-5681: pull request updates and more test coverage

* BCDA-5681: more unit test coverage
  • Loading branch information
krobertson3314 authored Jul 13, 2022
1 parent 08995ce commit 98928a7
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 90 deletions.
21 changes: 12 additions & 9 deletions bcda/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-chi/chi"

"github.com/CMSgov/bcda-app/bcda/database"
customErrors "github.com/CMSgov/bcda-app/bcda/errors"
"github.com/CMSgov/bcda-app/bcda/models/postgres"
responseutils "github.com/CMSgov/bcda-app/bcda/responseutils"
responseutilsv2 "github.com/CMSgov/bcda-app/bcda/responseutils/v2"
Expand Down Expand Up @@ -64,20 +65,13 @@ func ParseToken(next http.Handler) http.Handler {
return
}

// TODO (BCDA-3412): Remove this reference once we've captured all of the necessary
// logic into a service method.
db := database.Connection

repository := postgres.NewRepository(db)

var ad AuthData
if claims, ok := token.Claims.(*CommonClaims); ok && token.Valid {
switch claims.Issuer {
case "ssas":
ad, err = adFromClaims(repository, claims)
ad, err = GetProvider().getAuthDataFromClaims(claims)
if err != nil {
log.Auth.Error(err)
rw.Exception(w, http.StatusUnauthorized, responseutils.TokenErr, "")
handleSsasAuthDataError(w, rw, err)
return
}
default:
Expand All @@ -92,6 +86,15 @@ func ParseToken(next http.Handler) http.Handler {
})
}

func handleSsasAuthDataError(w http.ResponseWriter, rw fhirResponseWriter, err error) {
log.Auth.Error(err)
if _, ok := err.(*customErrors.EntityNotFoundError); ok {
rw.Exception(w, http.StatusForbidden, responseutils.UnauthorizedErr, responseutils.UnknownEntityErr)
} else {
rw.Exception(w, http.StatusUnauthorized, responseutils.TokenErr, "")
}
}

func RequireTokenAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := getRespWriter(r.URL.Path)
Expand Down
147 changes: 135 additions & 12 deletions bcda/auth/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"strconv"
"testing"

customErrors "github.com/CMSgov/bcda-app/bcda/errors"
"github.com/CMSgov/bcda-app/bcda/models/postgres/postgrestest"
responseutils "github.com/CMSgov/bcda-app/bcda/responseutils"
"github.com/CMSgov/bcda-app/bcda/testUtils"
"github.com/dgrijalva/jwt-go"

Expand Down Expand Up @@ -55,7 +57,8 @@ func (s *MiddlewareTestSuite) TearDownTest() {
s.server.Close()
}

func (s *MiddlewareTestSuite) TestRequireTokenAuthWithInvalidSignature() {
//integration test: makes HTTP request & asserts HTTP response
func (s *MiddlewareTestSuite) Test_InvalidTokenAuthWithInvalidSignature_Return404() {
client := s.server.Client()
badToken := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6ImlUcVhYSTB6YkFuSkNLRGFvYmZoa00xZi02ck1TcFRmeVpNUnBfMnRLSTgifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.cJOP_w-hBqnyTsBm3T6lOE5WpcHaAkLuQGAs1QO-lg2eWs8yyGW8p9WagGjxgvx7h9X72H7pXmXqej3GdlVbFmhuzj45A9SXDOAHZ7bJXwM1VidcPi7ZcrsMSCtP1hiN"

Expand All @@ -73,7 +76,8 @@ func (s *MiddlewareTestSuite) TestRequireTokenAuthWithInvalidSignature() {
assert.Nil(s.T(), err)
}

func (s *MiddlewareTestSuite) TestRequireTokenAuthWithInvalidToken() {
//unit test
func (s *MiddlewareTestSuite) Test_RequireTokenAuth_InvalidToken_Return401() {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/", s.server.URL), nil)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -103,8 +107,17 @@ func (s *MiddlewareTestSuite) TestRequireTokenAuthWithInvalidToken() {
mock.AssertExpectations(s.T())
}

func (s *MiddlewareTestSuite) TestRequireTokenAuthWithValidToken() {
//integration test: makes HTTP request & asserts HTTP response
func (s *MiddlewareTestSuite) Test_AuthMiddleware_ValidBearerTokenSupplied_Response200() {
bearerString := uuid.New()

tokenID, acoID := uuid.NewRandom().String(), uuid.NewRandom().String()

authData := auth.AuthData{
ACOID: acoID,
TokenID: tokenID,
}

token := &jwt.Token{
Claims: &auth.CommonClaims{
StandardClaims: jwt.StandardClaims{
Expand All @@ -119,6 +132,7 @@ func (s *MiddlewareTestSuite) TestRequireTokenAuthWithValidToken() {

mock := &auth.MockProvider{}
mock.On("VerifyToken", bearerString).Return(token, nil)
mock.On("getAuthDataFromClaims", token.Claims).Return(authData, nil)
mock.On("AuthorizeAccess", token.Raw).Return(nil)
auth.SetMockProvider(s.T(), mock)

Expand All @@ -141,10 +155,118 @@ func (s *MiddlewareTestSuite) TestRequireTokenAuthWithValidToken() {
mock.AssertExpectations(s.T())
}

func (s *MiddlewareTestSuite) TestRequireTokenAuthWithEmptyToken() {
func (s *MiddlewareTestSuite) Test_AuthMiddleware_SsasAuthDataError_EntityNotFoundType_Response403() {
cmsID := testUtils.RandomHexID()[0:4]

bearerString := uuid.New()

tokenID, acoID := uuid.NewRandom().String(), uuid.NewRandom().String()

authData := auth.AuthData{
ACOID: acoID,
TokenID: tokenID,
}

token := &jwt.Token{
Claims: &auth.CommonClaims{
StandardClaims: jwt.StandardClaims{
Issuer: "ssas",
},
ClientID: uuid.New(),
SystemID: uuid.New(),
Data: fmt.Sprintf(`{"cms_ids":["%s"]}`, cmsID),
},
Raw: uuid.New(),
Valid: true}

//custom error expected
dbErr := errors.New("DB Error: ACO Does Not Exist!")
entityNotFoundError := &customErrors.EntityNotFoundError{Err: dbErr, CMSID: cmsID}

mock := &auth.MockProvider{}
mock.On("VerifyToken", bearerString).Return(token, nil)
mock.On("getAuthDataFromClaims", token.Claims).Return(authData, entityNotFoundError)
auth.SetMockProvider(s.T(), mock)

client := s.server.Client()

// Valid token should return a 403 response
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/", s.server.URL), nil)
if err != nil {
log.Fatal(err)
}

req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", bearerString))

s.rr = httptest.NewRecorder()

resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}

assert.Equal(s.T(), 403, resp.StatusCode)
assert.Contains(s.T(), testUtils.ReadResponseBody(resp), responseutils.UnknownEntityErr)

mock.AssertExpectations(s.T())
}

func (s *MiddlewareTestSuite) Test_AuthMiddleware_SsasAuthDataError_OtherType_Response401() {
cmsID := testUtils.RandomHexID()[0:4]

bearerString := uuid.New()

tokenID, acoID := uuid.NewRandom().String(), uuid.NewRandom().String()

authData := auth.AuthData{
ACOID: acoID,
TokenID: tokenID,
}

token := &jwt.Token{
Claims: &auth.CommonClaims{
StandardClaims: jwt.StandardClaims{
Issuer: "ssas",
},
ClientID: uuid.New(),
SystemID: uuid.New(),
Data: fmt.Sprintf(`{"cms_ids":["%s"]}`, cmsID),
},
Raw: uuid.New(),
Valid: true}

//custom error expected
thrownErr := errors.New("error123")

mock := &auth.MockProvider{}
mock.On("VerifyToken", bearerString).Return(token, nil)
mock.On("getAuthDataFromClaims", token.Claims).Return(authData, thrownErr)
auth.SetMockProvider(s.T(), mock)

client := s.server.Client()

// Valid token should return a 401 response
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/", s.server.URL), nil)
if err != nil {
log.Fatal(err)
}

req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", bearerString))

resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
assert.Equal(s.T(), 401, resp.StatusCode)
//assert.Equal(s.T(), responseutils.UnknownEntityErr, s.rr.Body.String())

mock.AssertExpectations(s.T())
}

//integration test: makes HTTP request & asserts HTTP response
func (s *MiddlewareTestSuite) Test_AuthMiddleware_NoBearerTokenSupplied_Response401() {
client := s.server.Client()

// Valid token should return a 200 response
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/", s.server.URL), nil)
if err != nil {
log.Fatal(err)
Expand All @@ -159,9 +281,9 @@ func (s *MiddlewareTestSuite) TestRequireTokenAuthWithEmptyToken() {
assert.Equal(s.T(), 401, resp.StatusCode)
}

func (s *MiddlewareTestSuite) TestRequireTokenJobMatchWithMistmatchingData() {
//integration test: involves db connection to postgres
func (s *MiddlewareTestSuite) Test_RequireTokenJobMatch_MismatchingDataProvided_Return404() {
db := database.Connection

j := models.Job{
ACOID: uuid.Parse("DBBD1CE1-AE24-435C-807D-ED45953077D3"),
RequestURL: "/api/v1/ExplanationOfBenefit/$export",
Expand Down Expand Up @@ -206,7 +328,8 @@ func (s *MiddlewareTestSuite) TestRequireTokenJobMatchWithMistmatchingData() {
}
}

func (s *MiddlewareTestSuite) TestRequireTokenJobMatchWithRightACO() {
//integration test: involves db connection to postgres
func (s *MiddlewareTestSuite) Test_RequireTokenJobMatch_WithCorrectACOandJob_Return200() {
db := database.Connection

j := models.Job{
Expand Down Expand Up @@ -238,9 +361,8 @@ func (s *MiddlewareTestSuite) TestRequireTokenJobMatchWithRightACO() {
assert.Equal(s.T(), 200, s.rr.Code)
}

// TestRequireTokenACOMatchInvalidToken validates that we return a 404
// If the caller does not supply the auth data
func (s *MiddlewareTestSuite) TestRequireTokenACOMatchInvalidToken() {
//integration test: involves db connection to postgres
func (s *MiddlewareTestSuite) Test_RequireTokenJobMatch_noAuthDataProvidedInContext_Return404() {
db := database.Connection

j := models.Job{
Expand All @@ -266,7 +388,8 @@ func (s *MiddlewareTestSuite) TestRequireTokenACOMatchInvalidToken() {
assert.Equal(s.T(), http.StatusNotFound, s.rr.Code)
}

func (s *MiddlewareTestSuite) TestCheckBlacklist() {
//unit test
func (s *MiddlewareTestSuite) Test_CheckBlacklist() {
blacklisted := testUtils.RandomHexID()[0:4]
notBlacklisted := testUtils.RandomHexID()[0:4]

Expand Down
69 changes: 34 additions & 35 deletions bcda/auth/mock_provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions bcda/auth/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,7 @@ type Provider interface {

// GetVersion gets the version of the provider
GetVersion() (string, error)

//getAuthDataFromClaims gets AuthData from Token Claims
getAuthDataFromClaims(*CommonClaims) (AuthData, error)
}
Loading

0 comments on commit 98928a7

Please sign in to comment.