github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/api/auth_test.go (about)

     1  package api_test
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  
     7  	"github.com/golang/mock/gomock"
     8  	. "github.com/onsi/ginkgo/v2"
     9  	"github.com/sirupsen/logrus"
    10  
    11  	"github.com/pyroscope-io/pyroscope/pkg/api"
    12  	"github.com/pyroscope-io/pyroscope/pkg/api/mocks"
    13  	"github.com/pyroscope-io/pyroscope/pkg/api/router"
    14  	"github.com/pyroscope-io/pyroscope/pkg/model"
    15  )
    16  
    17  var _ = Describe("AuthMiddleware", func() {
    18  	defer GinkgoRecover()
    19  
    20  	var (
    21  		// Mocks setup.
    22  		ctrl            *gomock.Controller
    23  		server          *httptest.Server
    24  		authServiceMock *mocks.MockAuthService
    25  
    26  		// The service is a sample target.
    27  		apiKeyServiceMock *mocks.MockAPIKeyService
    28  	)
    29  
    30  	BeforeEach(func() {
    31  		ctrl = gomock.NewController(GinkgoT())
    32  		authServiceMock = mocks.NewMockAuthService(ctrl)
    33  		apiKeyServiceMock = mocks.NewMockAPIKeyService(ctrl)
    34  		server = httptest.NewServer(newTestRouter(defaultReqCtx, router.Services{
    35  			Logger:        logrus.StandardLogger(),
    36  			AuthService:   authServiceMock,
    37  			APIKeyService: apiKeyServiceMock,
    38  		}))
    39  	})
    40  
    41  	AfterEach(func() {
    42  		ctrl.Finish()
    43  		server.Close()
    44  	})
    45  
    46  	Describe("request authentication", func() {
    47  		var (
    48  			// API key and the token string returned by mocked service.
    49  			expectedAPIKey model.APIKey
    50  			expectedUser   model.User
    51  			expectedToken  string
    52  		)
    53  
    54  		BeforeEach(func() {
    55  			expectedToken = "some-token"
    56  			expectedAPIKey = model.APIKey{
    57  				Name: "test-api-key",
    58  				Role: model.AdminRole,
    59  			}
    60  			expectedUser = model.User{
    61  				Name: "test-user",
    62  				Role: model.AdminRole,
    63  			}
    64  		})
    65  
    66  		Context("when request has a valid API key in the header", func() {
    67  			It("authenticates request", func() {
    68  				authServiceMock.EXPECT().
    69  					APIKeyFromToken(gomock.Any(), expectedToken).
    70  					Return(expectedAPIKey, nil).
    71  					Times(1)
    72  
    73  				apiKeyServiceMock.EXPECT().
    74  					GetAllAPIKeys(gomock.Any()).
    75  					Times(1)
    76  
    77  				req := newRequest(http.MethodGet, server.URL+"/keys", "")
    78  				req.Header.Set("Authorization", "Bearer "+expectedToken)
    79  				expectResponse(req,
    80  					"response_empty_array.json",
    81  					http.StatusOK)
    82  			})
    83  		})
    84  
    85  		Context("when request has an invalid API key in the header", func() {
    86  			It("returns status code Unauthorized", func() {
    87  				authServiceMock.EXPECT().
    88  					APIKeyFromToken(gomock.Any(), expectedToken).
    89  					Return(expectedAPIKey, model.ErrAPIKeyNotFound).
    90  					Times(1)
    91  
    92  				authServiceMock.EXPECT().
    93  					UserFromJWTToken(gomock.Any(), expectedToken).
    94  					Times(0)
    95  
    96  				apiKeyServiceMock.EXPECT().
    97  					GetAllAPIKeys(gomock.Any()).
    98  					Times(0)
    99  
   100  				req := newRequest(http.MethodGet, server.URL+"/keys", "")
   101  				req.Header.Set("Authorization", "Bearer "+expectedToken)
   102  				expectResponse(req,
   103  					"response_invalid_credentials.json",
   104  					http.StatusUnauthorized)
   105  			})
   106  		})
   107  
   108  		Context("when request has a valid user token in the cookies", func() {
   109  			It("authenticates request", func() {
   110  				authServiceMock.EXPECT().
   111  					UserFromJWTToken(gomock.Any(), expectedToken).
   112  					Return(expectedUser, nil).
   113  					Times(1)
   114  
   115  				authServiceMock.EXPECT().
   116  					APIKeyFromToken(gomock.Any(), expectedToken).
   117  					Times(0)
   118  
   119  				apiKeyServiceMock.EXPECT().
   120  					GetAllAPIKeys(gomock.Any()).
   121  					Times(1)
   122  
   123  				req := newRequest(http.MethodGet, server.URL+"/keys", "")
   124  				req.AddCookie(&http.Cookie{Name: api.JWTCookieName, Value: expectedToken})
   125  				expectResponse(req,
   126  					"response_empty_array.json",
   127  					http.StatusOK)
   128  			})
   129  		})
   130  
   131  		Context("when user token is invalid or can not be found", func() {
   132  			It("redirects request", func() {
   133  				authServiceMock.EXPECT().
   134  					UserFromJWTToken(gomock.Any(), expectedToken).
   135  					Return(expectedUser, model.ErrUserNotFound).
   136  					Times(1)
   137  
   138  				authServiceMock.EXPECT().
   139  					APIKeyFromToken(gomock.Any(), expectedToken).
   140  					Times(0)
   141  
   142  				apiKeyServiceMock.EXPECT().
   143  					GetAllAPIKeys(gomock.Any()).
   144  					Times(0)
   145  
   146  				req := newRequest(http.MethodGet, server.URL+"/keys", "")
   147  				req.AddCookie(&http.Cookie{Name: api.JWTCookieName, Value: expectedToken})
   148  				expectResponse(req,
   149  					"", // Empty response body.
   150  					http.StatusTemporaryRedirect)
   151  			})
   152  		})
   153  
   154  		Context("when credentials are not provided", func() {
   155  			It("redirects request", func() {
   156  				authServiceMock.EXPECT().
   157  					APIKeyFromToken(gomock.Any(), expectedToken).
   158  					Return(expectedAPIKey, nil).
   159  					Times(0)
   160  
   161  				authServiceMock.EXPECT().
   162  					UserFromJWTToken(gomock.Any(), expectedToken).
   163  					Return(expectedUser, model.ErrUserNotFound).
   164  					Times(0)
   165  
   166  				apiKeyServiceMock.EXPECT().
   167  					GetAllAPIKeys(gomock.Any()).
   168  					Times(0)
   169  
   170  				expectResponse(newRequest(http.MethodGet, server.URL+"/keys",
   171  					""), // Empty request body.
   172  					"", // Empty response.
   173  					http.StatusTemporaryRedirect)
   174  			})
   175  		})
   176  	})
   177  })