github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/access_test.go (about)

     1  // Copyright 2018 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"strconv"
    13  	"testing"
    14  
    15  	"github.com/google/syzkaller/dashboard/dashapi"
    16  	"github.com/stretchr/testify/assert"
    17  	"google.golang.org/appengine/v2/user"
    18  )
    19  
    20  // TestAccessConfig checks that access level were properly assigned throughout the config.
    21  func TestAccessConfig(t *testing.T) {
    22  	config := getConfig(context.Background())
    23  	tests := []struct {
    24  		what  string
    25  		want  AccessLevel
    26  		level AccessLevel
    27  	}{
    28  		{"admin", AccessAdmin, config.Namespaces["access-admin"].AccessLevel},
    29  		{"admin/0", AccessAdmin, config.Namespaces["access-admin"].Reporting[0].AccessLevel},
    30  		{"admin/1", AccessAdmin, config.Namespaces["access-admin"].Reporting[1].AccessLevel},
    31  		{"user", AccessUser, config.Namespaces["access-user"].AccessLevel},
    32  		{"user/0", AccessAdmin, config.Namespaces["access-user"].Reporting[0].AccessLevel},
    33  		{"user/1", AccessUser, config.Namespaces["access-user"].Reporting[1].AccessLevel},
    34  		{"public", AccessPublic, config.Namespaces["access-public"].AccessLevel},
    35  		{"public/0", AccessUser, config.Namespaces["access-public"].Reporting[0].AccessLevel},
    36  		{"public/1", AccessPublic, config.Namespaces["access-public"].Reporting[1].AccessLevel},
    37  	}
    38  	for _, test := range tests {
    39  		if test.level != test.want {
    40  			t.Errorf("%v level %v, want %v", test.what, test.level, test.want)
    41  		}
    42  	}
    43  }
    44  
    45  // TestAccess checks that all UIs respect access levels.
    46  // nolint: funlen, goconst, gocyclo
    47  func TestAccess(t *testing.T) {
    48  	c := NewCtx(t)
    49  	defer c.Close()
    50  
    51  	// entity describes pages/bugs/texts/etc.
    52  	type entity struct {
    53  		level AccessLevel // level on which this entity must be visible.
    54  		ref   string      // a unique entity reference id.
    55  		url   string      // url at which this entity can be requested.
    56  	}
    57  	entities := []entity{
    58  		// Main pages.
    59  		{
    60  			level: AccessAdmin,
    61  			url:   "/admin",
    62  		},
    63  		{
    64  			level: AccessPublic,
    65  			url:   "/access-public",
    66  		},
    67  		{
    68  			level: AccessPublic,
    69  			url:   "/access-public/fixed",
    70  		},
    71  		{
    72  			level: AccessPublic,
    73  			url:   "/access-public/invalid",
    74  		},
    75  		{
    76  			level: AccessPublic,
    77  			url:   "/access-public/graph/bugs",
    78  		},
    79  		{
    80  			level: AccessPublic,
    81  			url:   "/access-public/graph/lifetimes",
    82  		},
    83  		{
    84  			level: AccessPublic,
    85  			url:   "/access-public/graph/fuzzing",
    86  		},
    87  		{
    88  			level: AccessPublic,
    89  			url:   "/access-public/graph/crashes",
    90  		},
    91  		{
    92  			level: AccessUser,
    93  			url:   "/access-user",
    94  		},
    95  		{
    96  			level: AccessUser,
    97  			url:   "/access-user/fixed",
    98  		},
    99  		{
   100  			level: AccessUser,
   101  			url:   "/access-user/invalid",
   102  		},
   103  		{
   104  			level: AccessUser,
   105  			url:   "/access-user/graph/bugs",
   106  		},
   107  		{
   108  			level: AccessUser,
   109  			url:   "/access-user/graph/lifetimes",
   110  		},
   111  		{
   112  			level: AccessUser,
   113  			url:   "/access-user/graph/fuzzing",
   114  		},
   115  		{
   116  			level: AccessUser,
   117  			url:   "/access-user/graph/crashes",
   118  		},
   119  		{
   120  			level: AccessAdmin,
   121  			url:   "/access-admin",
   122  		},
   123  		{
   124  			level: AccessAdmin,
   125  			url:   "/access-admin/fixed",
   126  		},
   127  		{
   128  			level: AccessAdmin,
   129  			url:   "/access-admin/invalid",
   130  		},
   131  		{
   132  			level: AccessAdmin,
   133  			url:   "/access-admin/graph/bugs",
   134  		},
   135  		{
   136  			level: AccessAdmin,
   137  			url:   "/access-admin/graph/lifetimes",
   138  		},
   139  		{
   140  			level: AccessAdmin,
   141  			url:   "/access-admin/graph/fuzzing",
   142  		},
   143  		{
   144  			level: AccessAdmin,
   145  			url:   "/access-admin/graph/crashes",
   146  		},
   147  		{
   148  			// Any references to namespace, reporting, links, etc.
   149  			level: AccessUser,
   150  			ref:   "access-user",
   151  		},
   152  		{
   153  			// Any references to namespace, reporting, links, etc.
   154  			level: AccessAdmin,
   155  			ref:   "access-admin",
   156  		},
   157  	}
   158  
   159  	// noteBugAccessLevel collects all entities associated with the extID bug.
   160  	noteBugAccessLevel := func(extID string, level, nsLevel AccessLevel) {
   161  		bug, _, err := findBugByReportingID(c.ctx, extID)
   162  		c.expectOK(err)
   163  		crash, _, err := findCrashForBug(c.ctx, bug)
   164  		c.expectOK(err)
   165  		bugID := bug.keyHash(c.ctx)
   166  		entities = append(entities, []entity{
   167  			{
   168  				level: level,
   169  				ref:   bugID,
   170  				url:   fmt.Sprintf("/bug?id=%v", bugID),
   171  			},
   172  			{
   173  				level: level,
   174  				ref:   bug.Reporting[0].ID,
   175  				url:   fmt.Sprintf("/bug?extid=%v", bug.Reporting[0].ID),
   176  			},
   177  			{
   178  				level: level,
   179  				ref:   bug.Reporting[1].ID,
   180  				url:   fmt.Sprintf("/bug?extid=%v", bug.Reporting[1].ID),
   181  			},
   182  			{
   183  				level: level,
   184  				ref:   fmt.Sprint(crash.Log),
   185  				url:   fmt.Sprintf("/text?tag=CrashLog&id=%v", crash.Log),
   186  			},
   187  			{
   188  				level: level,
   189  				ref:   fmt.Sprint(crash.Log),
   190  				url: fmt.Sprintf("/text?tag=CrashLog&x=%v",
   191  					strconv.FormatUint(uint64(crash.Log), 16)),
   192  			},
   193  			{
   194  				level: level,
   195  				ref:   fmt.Sprint(crash.Report),
   196  				url:   fmt.Sprintf("/text?tag=CrashReport&id=%v", crash.Report),
   197  			},
   198  			{
   199  				level: level,
   200  				ref:   fmt.Sprint(crash.Report),
   201  				url: fmt.Sprintf("/text?tag=CrashReport&x=%v",
   202  					strconv.FormatUint(uint64(crash.Report), 16)),
   203  			},
   204  			{
   205  				level: level,
   206  				ref:   fmt.Sprint(crash.ReproC),
   207  				url:   fmt.Sprintf("/text?tag=ReproC&id=%v", crash.ReproC),
   208  			},
   209  			{
   210  				level: level,
   211  				ref:   fmt.Sprint(crash.ReproC),
   212  				url: fmt.Sprintf("/text?tag=ReproC&x=%v",
   213  					strconv.FormatUint(uint64(crash.ReproC), 16)),
   214  			},
   215  			{
   216  				level: level,
   217  				ref:   fmt.Sprint(crash.ReproSyz),
   218  				url:   fmt.Sprintf("/text?tag=ReproSyz&id=%v", crash.ReproSyz),
   219  			},
   220  			{
   221  				level: level,
   222  				ref:   fmt.Sprint(crash.ReproSyz),
   223  				url: fmt.Sprintf("/text?tag=ReproSyz&x=%v",
   224  					strconv.FormatUint(uint64(crash.ReproSyz), 16)),
   225  			},
   226  			{
   227  				level: level,
   228  				ref:   fmt.Sprint(crash.ReproLog),
   229  				url:   fmt.Sprintf("/text?tag=ReproLog&id=%v", crash.ReproLog),
   230  			},
   231  			{
   232  				level: level,
   233  				ref:   fmt.Sprint(crash.ReproLog),
   234  				url: fmt.Sprintf("/text?tag=ReproLog&x=%v",
   235  					strconv.FormatUint(uint64(crash.ReproLog), 16)),
   236  			},
   237  			{
   238  				level: nsLevel,
   239  				ref:   fmt.Sprint(crash.MachineInfo),
   240  				url:   fmt.Sprintf("/text?tag=MachineInfo&id=%v", crash.MachineInfo),
   241  			},
   242  			{
   243  				level: nsLevel,
   244  				ref:   fmt.Sprint(crash.MachineInfo),
   245  				url: fmt.Sprintf("/text?tag=MachineInfo&x=%v",
   246  					strconv.FormatUint(uint64(crash.MachineInfo), 16)),
   247  			},
   248  		}...)
   249  		for _, asset := range crash.Assets {
   250  			if asset.FsckLog != 0 {
   251  				entities = append(entities, entity{
   252  					level: level,
   253  					ref:   fmt.Sprint(crash.MachineInfo),
   254  					url:   fmt.Sprintf("/x/fsck.log?x=%x", uint64(asset.FsckLog)),
   255  				})
   256  			}
   257  		}
   258  	}
   259  
   260  	// noteBuildAccessLevel collects all entities associated with the kernel build buildID.
   261  	noteBuildAccessLevel := func(ns, buildID string) {
   262  		build, err := loadBuild(c.ctx, ns, buildID)
   263  		c.expectOK(err)
   264  		entities = append(entities, entity{
   265  			level: c.config().Namespaces[ns].AccessLevel,
   266  			ref:   build.ID,
   267  			url:   fmt.Sprintf("/text?tag=KernelConfig&id=%v", build.KernelConfig),
   268  		})
   269  	}
   270  
   271  	// These strings are put into crash log/report, kernel config, etc.
   272  	// If a request at level UserPublic sees a page containing "access-user",
   273  	// that will be flagged as error.
   274  	accessLevelPrefix := func(level AccessLevel) string {
   275  		switch level {
   276  		case AccessPublic:
   277  			return "access-public-"
   278  		case AccessUser:
   279  			return "access-user-"
   280  		default:
   281  			return "access-admin-"
   282  		}
   283  	}
   284  
   285  	// For each namespace we create 8 bugs:
   286  	// invalid, dup, fixed and open for both reportings.
   287  	// Bugs are setup in such a way that there are lots of
   288  	// duplicate/similar cross-references.
   289  	for _, ns := range []string{"access-admin", "access-user", "access-public"} {
   290  		clientName, clientKey := "", ""
   291  		for k, v := range c.config().Namespaces[ns].Clients {
   292  			clientName, clientKey = k, v
   293  		}
   294  		nsLevel := c.config().Namespaces[ns].AccessLevel
   295  		namespaceAccessPrefix := accessLevelPrefix(nsLevel)
   296  		client := c.makeClient(clientName, clientKey, true)
   297  		build := testBuild(1)
   298  		build.KernelConfig = []byte(namespaceAccessPrefix + "build")
   299  		client.UploadBuild(build)
   300  		noteBuildAccessLevel(ns, build.ID)
   301  
   302  		for reportingIdx := 0; reportingIdx < 2; reportingIdx++ {
   303  			accessLevel := c.config().Namespaces[ns].Reporting[reportingIdx].AccessLevel
   304  			accessPrefix := accessLevelPrefix(accessLevel)
   305  
   306  			crashInvalid := testCrashWithRepro(build, reportingIdx*10+0)
   307  			client.ReportCrash(crashInvalid)
   308  			repInvalid := client.pollBug()
   309  			if reportingIdx != 0 {
   310  				client.updateBug(repInvalid.ID, dashapi.BugStatusUpstream, "")
   311  				repInvalid = client.pollBug()
   312  			}
   313  			client.updateBug(repInvalid.ID, dashapi.BugStatusInvalid, "")
   314  			// Invalid bugs become visible up to the last reporting.
   315  			finalLevel := c.config().Namespaces[ns].
   316  				Reporting[len(c.config().Namespaces[ns].Reporting)-1].AccessLevel
   317  			noteBugAccessLevel(repInvalid.ID, finalLevel, nsLevel)
   318  
   319  			crashFixed := testCrashWithRepro(build, reportingIdx*10+0)
   320  			client.ReportCrash(crashFixed)
   321  			repFixed := client.pollBug()
   322  			if reportingIdx != 0 {
   323  				client.updateBug(repFixed.ID, dashapi.BugStatusUpstream, "")
   324  				repFixed = client.pollBug()
   325  			}
   326  			reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
   327  				ID:         repFixed.ID,
   328  				Status:     dashapi.BugStatusOpen,
   329  				FixCommits: []string{ns + "-patch0"},
   330  				ExtID:      accessPrefix + "reporting-ext-id",
   331  				Link:       accessPrefix + "reporting-link",
   332  			})
   333  			c.expectEQ(reply.OK, true)
   334  			buildFixing := testBuild(reportingIdx*10 + 2)
   335  			buildFixing.Manager = build.Manager
   336  			buildFixing.Commits = []string{ns + "-patch0"}
   337  			client.UploadBuild(buildFixing)
   338  			noteBuildAccessLevel(ns, buildFixing.ID)
   339  			// Fixed bugs are also visible up to the last reporting.
   340  			noteBugAccessLevel(repFixed.ID, finalLevel, nsLevel)
   341  
   342  			crashOpen := testCrashWithRepro(build, reportingIdx*10+0)
   343  			crashOpen.Log = []byte(accessPrefix + "log")
   344  			crashOpen.Report = []byte(accessPrefix + "report")
   345  			crashOpen.ReproC = []byte(accessPrefix + "repro c")
   346  			crashOpen.ReproSyz = []byte(accessPrefix + "repro syz")
   347  			crashOpen.ReproLog = []byte(accessPrefix + "repro log")
   348  			crashOpen.MachineInfo = []byte(ns + "machine info")
   349  			crashOpen.Assets = []dashapi.NewAsset{
   350  				{
   351  					DownloadURL: "http://a.b",
   352  					Type:        dashapi.MountInRepro,
   353  					FsckLog:     []byte("fsck log"),
   354  				},
   355  			}
   356  			client.ReportCrash(crashOpen)
   357  			repOpen := client.pollBug()
   358  			if reportingIdx != 0 {
   359  				client.updateBug(repOpen.ID, dashapi.BugStatusUpstream, "")
   360  				repOpen = client.pollBug()
   361  			}
   362  			noteBugAccessLevel(repOpen.ID, accessLevel, nsLevel)
   363  
   364  			crashPatched := testCrashWithRepro(build, reportingIdx*10+1)
   365  			client.ReportCrash(crashPatched)
   366  			repPatched := client.pollBug()
   367  			if reportingIdx != 0 {
   368  				client.updateBug(repPatched.ID, dashapi.BugStatusUpstream, "")
   369  				repPatched = client.pollBug()
   370  			}
   371  			reply, _ = client.ReportingUpdate(&dashapi.BugUpdate{
   372  				ID:         repPatched.ID,
   373  				Status:     dashapi.BugStatusOpen,
   374  				FixCommits: []string{ns + "-patch0"},
   375  				ExtID:      accessPrefix + "reporting-ext-id",
   376  				Link:       accessPrefix + "reporting-link",
   377  			})
   378  			c.expectEQ(reply.OK, true)
   379  			// Patched bugs are also visible up to the last reporting.
   380  			noteBugAccessLevel(repPatched.ID, finalLevel, nsLevel)
   381  
   382  			crashDup := testCrashWithRepro(build, reportingIdx*10+2)
   383  			client.ReportCrash(crashDup)
   384  			repDup := client.pollBug()
   385  			if reportingIdx != 0 {
   386  				client.updateBug(repDup.ID, dashapi.BugStatusUpstream, "")
   387  				repDup = client.pollBug()
   388  			}
   389  			client.updateBug(repDup.ID, dashapi.BugStatusDup, repOpen.ID)
   390  			noteBugAccessLevel(repDup.ID, accessLevel, nsLevel)
   391  		}
   392  	}
   393  
   394  	// checkReferences checks that page contents do not contain
   395  	// references to entities that must not be visible.
   396  	checkReferences := func(t *testing.T, url string, requestLevel AccessLevel, reply []byte) {
   397  		for _, ent := range entities {
   398  			if requestLevel >= ent.level || ent.ref == "" {
   399  				continue
   400  			}
   401  			if bytes.Contains(reply, []byte(ent.ref)) {
   402  				t.Errorf("request %v at level %v contains ref %v at level %v:\n%s",
   403  					url, requestLevel, ent.ref, ent.level, reply)
   404  			}
   405  		}
   406  	}
   407  
   408  	// checkPage checks that the page at url is accessible/not accessible as required.
   409  	checkPage := func(t *testing.T, requestLevel, pageLevel AccessLevel, url string) []byte {
   410  		reply, err := c.AuthGET(requestLevel, url)
   411  		if requestLevel >= pageLevel {
   412  			assert.NoError(t, err)
   413  		} else if requestLevel == AccessPublic {
   414  			loginURL, err1 := user.LoginURL(c.ctx, url)
   415  			if err1 != nil {
   416  				t.Fatal(err1)
   417  			}
   418  			assert.NotNil(t, err)
   419  			var httpErr *HTTPError
   420  			assert.True(t, errors.As(err, &httpErr))
   421  			assert.Equal(t, httpErr.Code, http.StatusTemporaryRedirect)
   422  			assert.Equal(t, httpErr.Headers["Location"], []string{loginURL})
   423  		} else {
   424  			expectFailureStatus(t, err, http.StatusForbidden)
   425  		}
   426  		return reply
   427  	}
   428  
   429  	// Finally, request all entities at all access levels and
   430  	// check that we see only what we need to see.
   431  	for requestLevel := AccessPublic; requestLevel < AccessAdmin; requestLevel++ {
   432  		for i, ent := range entities {
   433  			if ent.url == "" {
   434  				continue
   435  			}
   436  			if testing.Short() && (requestLevel != AccessPublic || ent.level == AccessPublic) {
   437  				// In the short mode, only test that there's no public access to non-public URLs.
   438  				continue
   439  			}
   440  			t.Run(fmt.Sprintf("level%d_%d", requestLevel, i), func(t *testing.T) {
   441  				reply := checkPage(t, requestLevel, ent.level, ent.url)
   442  				checkReferences(t, ent.url, requestLevel, reply)
   443  			})
   444  		}
   445  	}
   446  }
   447  
   448  type UserAuthorizationLevel int
   449  
   450  const (
   451  	BadAuthDomain UserAuthorizationLevel = iota
   452  	Regular
   453  	Authenticated
   454  	AuthorizedAccessPublic
   455  	AuthorizedUser
   456  	AuthorizedAdmin
   457  )
   458  
   459  func makeUser(a UserAuthorizationLevel) *user.User {
   460  	u := &user.User{}
   461  	switch a {
   462  	case BadAuthDomain:
   463  		u.AuthDomain = "public.com"
   464  	case Regular:
   465  		u = nil
   466  	case Authenticated:
   467  		u.Email = "someuser@public.com"
   468  	case AuthorizedAccessPublic:
   469  		u.Email = "checked-email@public.com"
   470  	case AuthorizedUser:
   471  		u.Email = "customer@syzkaller.com"
   472  	case AuthorizedAdmin:
   473  		u.Email = "admin@syzkaller.com"
   474  		u.Admin = true
   475  	}
   476  	return u
   477  }
   478  
   479  func TestUserAccessLevel(t *testing.T) {
   480  	tests := []struct {
   481  		name                string
   482  		u                   *user.User
   483  		enforcedAccessLevel string
   484  		config              *GlobalConfig
   485  		wantAccessLevel     AccessLevel
   486  		wantIsAuthorized    bool
   487  	}{
   488  		{
   489  			name:            "wrong auth domain",
   490  			u:               makeUser(BadAuthDomain),
   491  			wantAccessLevel: AccessPublic,
   492  		},
   493  		{
   494  			name:            "regular not authenticated user",
   495  			u:               makeUser(Regular),
   496  			wantAccessLevel: AccessPublic,
   497  		},
   498  		{
   499  			name:                "regular not authenticated user wants to be an admin",
   500  			u:                   makeUser(Regular),
   501  			enforcedAccessLevel: "admin",
   502  			config:              testConfig,
   503  			wantAccessLevel:     AccessPublic,
   504  		},
   505  		{
   506  			name:                "regular not authenticated user wants to be a user",
   507  			u:                   makeUser(Regular),
   508  			enforcedAccessLevel: "user",
   509  			config:              testConfig,
   510  			wantAccessLevel:     AccessPublic,
   511  		},
   512  		{
   513  			name:            "authenticated, not authorized user",
   514  			u:               makeUser(Authenticated),
   515  			config:          testConfig,
   516  			wantAccessLevel: AccessPublic,
   517  		},
   518  		{
   519  			name:                "authenticated, not authorized user wants to be an admin",
   520  			u:                   makeUser(Authenticated),
   521  			enforcedAccessLevel: "admin",
   522  			config:              testConfig,
   523  			wantAccessLevel:     AccessPublic,
   524  		},
   525  		{
   526  			name:                "authenticated, not authorized user wants to be a user",
   527  			u:                   makeUser(Authenticated),
   528  			enforcedAccessLevel: "user",
   529  			config:              testConfig,
   530  			wantAccessLevel:     AccessPublic,
   531  		},
   532  		{
   533  			name:             "authorized for AccessPublic user",
   534  			u:                makeUser(AuthorizedAccessPublic),
   535  			config:           testConfig,
   536  			wantAccessLevel:  AccessPublic,
   537  			wantIsAuthorized: true,
   538  		},
   539  		{
   540  			name:                "authorized for AccessPublic user wants to be an admin",
   541  			u:                   makeUser(AuthorizedAccessPublic),
   542  			enforcedAccessLevel: "admin",
   543  			config:              testConfig,
   544  			wantAccessLevel:     AccessPublic,
   545  			wantIsAuthorized:    true,
   546  		},
   547  		{
   548  			name:                "authorized for AccessPublic user wants to be a user",
   549  			u:                   makeUser(AuthorizedAccessPublic),
   550  			enforcedAccessLevel: "user",
   551  			config:              testConfig,
   552  			wantAccessLevel:     AccessPublic,
   553  			wantIsAuthorized:    true,
   554  		},
   555  		{
   556  			name:             "authorized for AccessUser user",
   557  			u:                makeUser(AuthorizedUser),
   558  			config:           testConfig,
   559  			wantAccessLevel:  AccessUser,
   560  			wantIsAuthorized: true,
   561  		},
   562  		{
   563  			name:                "authorized for AccessUser user wants to be an admin",
   564  			u:                   makeUser(AuthorizedUser),
   565  			enforcedAccessLevel: "admin",
   566  			config:              testConfig,
   567  			wantAccessLevel:     AccessUser,
   568  			wantIsAuthorized:    true,
   569  		},
   570  		{
   571  			name:             "authorized admin wants AccessAdmin",
   572  			u:                makeUser(AuthorizedAdmin),
   573  			config:           testConfig,
   574  			wantAccessLevel:  AccessAdmin,
   575  			wantIsAuthorized: true,
   576  		},
   577  		{
   578  			name:                "authorized admin wants AccessPublic",
   579  			u:                   makeUser(AuthorizedAdmin),
   580  			enforcedAccessLevel: "public",
   581  			config:              testConfig,
   582  			wantAccessLevel:     AccessPublic,
   583  			wantIsAuthorized:    true,
   584  		},
   585  		{
   586  			name:                "authorized admin wants AccessUser",
   587  			u:                   makeUser(AuthorizedAdmin),
   588  			enforcedAccessLevel: "user",
   589  			config:              testConfig,
   590  			wantAccessLevel:     AccessUser,
   591  			wantIsAuthorized:    true,
   592  		},
   593  	}
   594  	for _, test := range tests {
   595  		t.Run(test.name, func(t *testing.T) {
   596  			gotIsAuthorized, gotAccessLevel := userAccessLevel(test.u, test.enforcedAccessLevel, test.config)
   597  			assert.Equal(t, test.wantAccessLevel, gotAccessLevel)
   598  			assert.Equal(t, test.wantIsAuthorized, gotIsAuthorized)
   599  		})
   600  	}
   601  }