...

Source file src/edge-infra.dev/pkg/sds/emergencyaccess/eagateway/server/server_test.go

Documentation: edge-infra.dev/pkg/sds/emergencyaccess/eagateway/server

     1  package server
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"path"
    10  	"testing"
    11  
    12  	"edge-infra.dev/pkg/sds/emergencyaccess/eaconst"
    13  	"edge-infra.dev/pkg/sds/emergencyaccess/eagateway"
    14  	"edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
    15  	"edge-infra.dev/pkg/sds/emergencyaccess/types"
    16  
    17  	"github.com/gin-gonic/gin"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  const (
    22  	badCommand = "false"
    23  )
    24  
    25  var (
    26  	defaultBytes = []byte(`
    27  {
    28  	"type": "Output",
    29  	"exitCode": 0,
    30  	"output": "hello\n",
    31  	"timestamp": "01-01-2023 00:00:00",
    32  	"duration": 0.1
    33  }`)
    34  	defaultAttrMap = map[string]string{
    35  		"bannerId":             "banner",
    36  		"storeId":              "store",
    37  		"terminalId":           "terminal",
    38  		"sessionId":            "orderingKey",
    39  		"identity":             "identity",
    40  		"version":              "1.0",
    41  		"signature":            "signature",
    42  		"request-message-uuid": "uuid",
    43  	}
    44  	defaultTarget = types.Target{
    45  		Projectid:  "projectID",
    46  		Bannerid:   "bannerID",
    47  		Storeid:    "storeID",
    48  		Terminalid: "terminalID",
    49  	}
    50  
    51  	errTestRCLIStartSessionFail = fmt.Errorf("TestRCLIStartSessionFail")
    52  )
    53  
    54  // helper function which sets well known auth headers to any request
    55  func setAuthHeaders(req *http.Request) {
    56  	req.Header.Set(eaconst.HeaderAuthKeyUsername, "user")
    57  	req.Header.Set(eaconst.HeaderAuthKeyEmail, "email")
    58  	req.Header.Set(eaconst.HeaderAuthKeyRoles, "role")
    59  	req.Header.Set(eaconst.HeaderAuthKeyBanners, "banner")
    60  }
    61  
    62  /*
    63  Mock auth service server
    64  */
    65  
    66  type httpmiddleware func(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request)
    67  
    68  type authserverOpts struct {
    69  	middleware []httpmiddleware
    70  
    71  	authorizeCommand func(w http.ResponseWriter, r *http.Request)
    72  	authorizeRequest func(w http.ResponseWriter, r *http.Request)
    73  	resolveTarget    func(w http.ResponseWriter, r *http.Request)
    74  	authorizeTarget  func(w http.ResponseWriter, r *http.Request)
    75  	authorizeUser    func(w http.ResponseWriter, r *http.Request)
    76  }
    77  
    78  type Option func(opts *authserverOpts)
    79  
    80  // verifyUserAuthHeaders returns middleware which can verify the correct user
    81  // Auth headers have been set, and then passes on the request to the passed in
    82  // handler func.
    83  func verifyUserAuthHeaders(t *testing.T) httpmiddleware {
    84  	return func(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
    85  		return func(w http.ResponseWriter, r *http.Request) {
    86  			assert.Equal(t, []string{"user"}, r.Header.Values(eaconst.HeaderAuthKeyUsername))
    87  			assert.Equal(t, []string{"email"}, r.Header.Values(eaconst.HeaderAuthKeyEmail))
    88  			assert.Equal(t, []string{"role"}, r.Header.Values(eaconst.HeaderAuthKeyRoles))
    89  			assert.Equal(t, []string{"banner"}, r.Header.Values(eaconst.HeaderAuthKeyBanners))
    90  
    91  			next(w, r)
    92  		}
    93  	}
    94  }
    95  
    96  // WithMiddleware allows setting middleware to run on every endpoint. The first
    97  // middleware passed in is the last applied middleware to the request
    98  func WithMiddleware(middleware ...httpmiddleware) Option {
    99  	return func(opts *authserverOpts) {
   100  		opts.middleware = middleware
   101  	}
   102  }
   103  
   104  func defaultAuthorizeRequest(status int) func(w http.ResponseWriter, r *http.Request) {
   105  	return func(w http.ResponseWriter, r *http.Request) {
   106  		if status != http.StatusOK {
   107  			// If ok we don't want to write it immediately, otherwise any errors
   108  			// would be hiddent as you can't write the header multiple times
   109  			w.WriteHeader(status)
   110  		}
   111  
   112  		// First read the incoming body and find the command in the body
   113  		bytes, err := io.ReadAll(r.Body)
   114  		if err != nil {
   115  			w.WriteHeader(http.StatusInternalServerError)
   116  			return
   117  		}
   118  
   119  		var m map[string]map[string]json.RawMessage
   120  		err = json.Unmarshal(bytes, &m)
   121  		if err != nil {
   122  			w.WriteHeader(http.StatusInternalServerError)
   123  			return
   124  		}
   125  
   126  		var n map[string]string
   127  		err = json.Unmarshal(m["Request"]["Data"], &n)
   128  		if err != nil {
   129  			w.WriteHeader(http.StatusInternalServerError)
   130  			return
   131  		}
   132  
   133  		// if the command is false we want to send an unauthorized response
   134  		if n["command"] == "false" {
   135  			// Although we return statusUnauthorized here, the error code
   136  			// corresponds to status forbidden. This tests the functionality of
   137  			// the apierror handler
   138  			w.WriteHeader(http.StatusUnauthorized)
   139  			_, _ = w.Write([]byte(`{
   140  				"errorCode": 62001,
   141  				"ErrorMessage": "User Authorization Failure - User not permitted to perform this action"
   142  			}`))
   143  			return
   144  		}
   145  
   146  		// Otherwise generate a valid request message and respond with a valid
   147  		// response payload
   148  
   149  		req, err := msgdata.NewV1_0Request(defaultCommand)
   150  		if err != nil {
   151  			w.WriteHeader(http.StatusInternalServerError)
   152  			return
   153  		}
   154  		d, err := req.Data()
   155  		if err != nil {
   156  			w.WriteHeader(http.StatusInternalServerError)
   157  			return
   158  		}
   159  
   160  		data := authRequestResponse{
   161  			Request: struct {
   162  				Data       json.RawMessage
   163  				Attributes map[string]string
   164  			}{
   165  				Data:       json.RawMessage(d),
   166  				Attributes: req.Attributes(),
   167  			},
   168  		}
   169  		resp, err := json.Marshal(data)
   170  		if err != nil {
   171  			w.WriteHeader(http.StatusInternalServerError)
   172  			return
   173  		}
   174  
   175  		_, err = w.Write(resp)
   176  		if err != nil {
   177  			w.WriteHeader(http.StatusInternalServerError)
   178  			return
   179  		}
   180  	}
   181  }
   182  
   183  func WithAuthorizeCommand(authorizeCommand func(w http.ResponseWriter, r *http.Request)) Option {
   184  	return func(opts *authserverOpts) {
   185  		opts.authorizeCommand = authorizeCommand
   186  	}
   187  }
   188  
   189  func defaultAuthorizeCommand(status int) func(w http.ResponseWriter, r *http.Request) {
   190  	return func(w http.ResponseWriter, r *http.Request) {
   191  		w.WriteHeader(status)
   192  		bytes, err := io.ReadAll(r.Body)
   193  		if err != nil {
   194  			w.WriteHeader(http.StatusInternalServerError)
   195  			return
   196  		}
   197  		var m map[string]interface{}
   198  		err = json.Unmarshal(bytes, &m)
   199  		if err != nil {
   200  			w.WriteHeader(http.StatusInternalServerError)
   201  			return
   202  		}
   203  		command, ok := m["Command"].(string)
   204  		if !ok {
   205  			w.WriteHeader(http.StatusInternalServerError)
   206  			return
   207  		}
   208  		validation := eagateway.CommandValidation{Valid: true}
   209  		if command == badCommand {
   210  			validation.Valid = false
   211  		}
   212  		resp, err := json.Marshal(validation)
   213  		if err != nil {
   214  			w.WriteHeader(http.StatusInternalServerError)
   215  			return
   216  		}
   217  		_, err = w.Write(resp)
   218  		if err != nil {
   219  			w.WriteHeader(http.StatusInternalServerError)
   220  			return
   221  		}
   222  	}
   223  }
   224  
   225  // Optionally override the default resolve target endpoint function
   226  func WithResolveTarget(resolveTarget func(w http.ResponseWriter, r *http.Request)) Option {
   227  	return func(opts *authserverOpts) {
   228  		opts.resolveTarget = resolveTarget
   229  	}
   230  }
   231  
   232  func defaultResolveTarget() func(w http.ResponseWriter, r *http.Request) {
   233  	return func(w http.ResponseWriter, _ *http.Request) {
   234  		data, _ := json.Marshal(map[string]types.Target{
   235  			"target": defaultTarget,
   236  		})
   237  		_, _ = w.Write(data)
   238  	}
   239  }
   240  
   241  // Optionally override the default authorize target endpoint function
   242  func WithAuthorizeTarget(authorizeTarget func(w http.ResponseWriter, r *http.Request)) Option {
   243  	return func(opts *authserverOpts) {
   244  		opts.authorizeTarget = authorizeTarget
   245  	}
   246  }
   247  
   248  func defaultAuthorizeTarget(status int) func(w http.ResponseWriter, r *http.Request) {
   249  	return func(w http.ResponseWriter, _ *http.Request) {
   250  		w.WriteHeader(status)
   251  	}
   252  }
   253  
   254  func WithAuthorizeUser(authorizeUser func(w http.ResponseWriter, r *http.Request)) Option {
   255  	return func(opts *authserverOpts) {
   256  		opts.authorizeUser = authorizeUser
   257  	}
   258  }
   259  
   260  func defaultAuthorizeUser(status int) func(w http.ResponseWriter, r *http.Request) {
   261  	return func(w http.ResponseWriter, _ *http.Request) {
   262  		w.WriteHeader(status)
   263  	}
   264  }
   265  
   266  func authserviceServer(status int, opts ...Option) (server *httptest.Server, url string) {
   267  	opt := authserverOpts{
   268  		authorizeCommand: defaultAuthorizeCommand(status),
   269  		authorizeRequest: defaultAuthorizeRequest(status),
   270  		authorizeTarget:  defaultAuthorizeTarget(status),
   271  		resolveTarget:    defaultResolveTarget(),
   272  		authorizeUser:    defaultAuthorizeUser(status),
   273  	}
   274  	for _, o := range opts {
   275  		o(&opt)
   276  	}
   277  
   278  	for _, mid := range opt.middleware {
   279  		opt.authorizeCommand = mid(opt.authorizeCommand)
   280  		opt.authorizeRequest = mid(opt.authorizeRequest)
   281  		opt.authorizeTarget = mid(opt.authorizeTarget)
   282  		opt.resolveTarget = mid(opt.resolveTarget)
   283  	}
   284  
   285  	mux := http.NewServeMux()
   286  	mux.HandleFunc("/authservice/authorizeCommand", opt.authorizeCommand)
   287  	mux.HandleFunc("/authservice/authorizeRequest", opt.authorizeRequest)
   288  	mux.HandleFunc("/authservice/authorizeTarget", opt.authorizeTarget)
   289  	mux.HandleFunc("/authservice/resolveTarget", opt.resolveTarget)
   290  	mux.HandleFunc("/authservice/authorizeUser", opt.authorizeUser)
   291  
   292  	server = httptest.NewServer(mux)
   293  	url = path.Join(server.URL[7:], "authservice")
   294  	return server, url
   295  }
   296  
   297  /*
   298  End mock
   299  */
   300  
   301  func TestServerStatusEndpoints(t *testing.T) {
   302  	tests := map[string]struct {
   303  		query   string
   304  		expRes  string
   305  		expCode int
   306  	}{
   307  		"Ready Returns Ok": {
   308  			"/ready",
   309  			`ok`,
   310  			200,
   311  		},
   312  		"Health Returns Ok": {
   313  			"/health",
   314  			`ok`,
   315  			200,
   316  		},
   317  	}
   318  
   319  	for name, tc := range tests {
   320  		t.Run(name, func(t *testing.T) {
   321  			r := httptest.NewRecorder()
   322  
   323  			// Create Gin context in test mode
   324  			gin.SetMode(gin.TestMode)
   325  			_, ginEngine := gin.CreateTestContext(r)
   326  
   327  			// Create new GatewayServer. Use nil for rcli and requestservice as
   328  			// this helps guarantee that the health endpoints don't make
   329  			// spurious calls to these components.
   330  			// Choose localhost as this does not require slow dns resolution, and no
   331  			// authservice should be running on localhost during this test
   332  			_, err := New(eagateway.Config{AuthServiceHost: "localhost"}, ginEngine, newLogger(), nil, nil)
   333  			assert.NoError(t, err)
   334  
   335  			// Send test query
   336  			req, err := http.NewRequest(http.MethodGet, tc.query, nil)
   337  			assert.NoError(t, err)
   338  			ginEngine.ServeHTTP(r, req)
   339  
   340  			// Retrieve response
   341  			res := r.Result()
   342  			assert.Equal(t, tc.expCode, res.StatusCode)
   343  			data, err := io.ReadAll(r.Body)
   344  			assert.NoError(t, err)
   345  
   346  			assert.Equal(t, tc.expRes, string(data))
   347  		})
   348  	}
   349  }
   350  

View as plain text