github.com/cs3org/reva/v2@v2.27.7/pkg/appauth/manager/json/json_test.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package json
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"encoding/json"
    25  	"io"
    26  	"os"
    27  	"reflect"
    28  	"testing"
    29  	"time"
    30  
    31  	"bou.ke/monkey"
    32  	apppb "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
    33  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    34  	typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    35  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    36  	"github.com/gdexlab/go-render/render"
    37  	"github.com/google/go-cmp/cmp"
    38  	"github.com/sethvargo/go-password/password"
    39  	"golang.org/x/crypto/bcrypt"
    40  	"google.golang.org/protobuf/testing/protocmp"
    41  )
    42  
    43  func TestNewManager(t *testing.T) {
    44  	userTest := &userpb.User{Id: &userpb.UserId{Idp: "0"}, Username: "Test User"}
    45  
    46  	// temp directory where are stored tests config files
    47  	tempDir := createTempDir(t, "jsonappauth_test")
    48  	defer os.RemoveAll(tempDir)
    49  
    50  	jsonCorruptedFile := createTempFile(t, tempDir, "corrupted.json")
    51  	defer jsonCorruptedFile.Close()
    52  	jsonEmptyFile := createTempFile(t, tempDir, "empty.json")
    53  	defer jsonEmptyFile.Close()
    54  	jsonOkFile := createTempFile(t, tempDir, "ok.json")
    55  	defer jsonOkFile.Close()
    56  
    57  	hashToken, _ := bcrypt.GenerateFromPassword([]byte("1234"), 10)
    58  
    59  	dummyData := map[string]map[string]*apppb.AppPassword{
    60  		userTest.GetId().String(): {
    61  			string(hashToken): {
    62  				Password:   string(hashToken),
    63  				TokenScope: nil,
    64  				Label:      "label",
    65  				User:       userTest.GetId(),
    66  				Expiration: nil,
    67  				Ctime:      &typespb.Timestamp{Seconds: 0},
    68  				Utime:      &typespb.Timestamp{Seconds: 0},
    69  			},
    70  		}}
    71  
    72  	dummyDataJSON, _ := json.Marshal(dummyData)
    73  
    74  	// fill temp file with tests data
    75  	fill(t, jsonCorruptedFile, `[{`)
    76  	fill(t, jsonEmptyFile, "")
    77  	fill(t, jsonOkFile, string(dummyDataJSON))
    78  
    79  	testCases := []struct {
    80  		description string
    81  		configMap   map[string]interface{}
    82  		expected    *jsonManager
    83  	}{
    84  		{
    85  			description: "New appauth manager from corrupted state file",
    86  			configMap: map[string]interface{}{
    87  				"file":           jsonCorruptedFile.Name(),
    88  				"token_strength": 10,
    89  			},
    90  			expected: nil, // nil == error
    91  		},
    92  		{
    93  			description: "New appauth manager from empty state file",
    94  			configMap: map[string]interface{}{
    95  				"file":               jsonEmptyFile.Name(),
    96  				"token_strength":     10,
    97  				"password_hash_cost": 12,
    98  			},
    99  			expected: &jsonManager{
   100  				config: &config{
   101  					File:             jsonEmptyFile.Name(),
   102  					TokenStrength:    10,
   103  					PasswordHashCost: 12,
   104  				},
   105  				passwords: map[string]map[string]*apppb.AppPassword{},
   106  			},
   107  		},
   108  		{
   109  			description: "New appauth manager from state file",
   110  			configMap: map[string]interface{}{
   111  				"file":               jsonOkFile.Name(),
   112  				"token_strength":     10,
   113  				"password_hash_cost": 10,
   114  			},
   115  			expected: &jsonManager{
   116  				config: &config{
   117  					File:             jsonOkFile.Name(),
   118  					TokenStrength:    10,
   119  					PasswordHashCost: 10,
   120  				},
   121  				passwords: dummyData,
   122  			},
   123  		},
   124  	}
   125  
   126  	for _, test := range testCases {
   127  		t.Run(test.description, func(t *testing.T) {
   128  			manager, err := New(test.configMap)
   129  			if test.expected == nil {
   130  				if err == nil {
   131  					t.Fatalf("no error (but we expected one) while get manager")
   132  				} else {
   133  					t.Skip()
   134  				}
   135  			}
   136  			if !reflect.DeepEqual(test.expected.config, manager.(*jsonManager).config) {
   137  				t.Fatalf("appauth differ: expected=%v got=%v", render.AsCode(test.expected), render.AsCode(manager))
   138  			}
   139  
   140  			comparePasswords(t, test.expected.passwords, manager.(*jsonManager).passwords)
   141  		})
   142  	}
   143  
   144  }
   145  
   146  func TestGenerateAppPassword(t *testing.T) {
   147  	userTest := &userpb.User{Id: &userpb.UserId{Idp: "0"}, Username: "Test User"}
   148  	ctx := ctxpkg.ContextSetUser(context.Background(), userTest)
   149  	tempDir := createTempDir(t, "jsonappauth_test")
   150  	defer os.RemoveAll(tempDir)
   151  
   152  	nowFixed := time.Date(2021, time.May, 21, 12, 21, 0, 0, time.UTC)
   153  	patchNow := monkey.Patch(time.Now, func() time.Time { return nowFixed })
   154  	now := now()
   155  	token := "1234"
   156  	patchPasswordGenerate := monkey.Patch(password.Generate, func(int, int, int, bool, bool) (string, error) { return token, nil })
   157  	defer patchNow.Unpatch()
   158  	defer patchPasswordGenerate.Unpatch()
   159  
   160  	generateFromPassword := monkey.Patch(bcrypt.GenerateFromPassword, func(pw []byte, n int) ([]byte, error) {
   161  		return append([]byte("hash:"), pw...), nil
   162  	})
   163  	defer generateFromPassword.Restore()
   164  	hashTokenXXXX, _ := bcrypt.GenerateFromPassword([]byte("XXXX"), 11)
   165  	hashToken1234, _ := bcrypt.GenerateFromPassword([]byte(token), 11)
   166  
   167  	dummyData := map[string]map[string]*apppb.AppPassword{
   168  		userpb.User{Id: &userpb.UserId{Idp: "1"}, Username: "Test User1"}.Id.String(): {
   169  			string(hashTokenXXXX): {
   170  				Password: string(hashTokenXXXX),
   171  				Label:    "",
   172  				User:     &userpb.UserId{Idp: "1"},
   173  				Ctime:    now,
   174  				Utime:    now,
   175  			},
   176  		},
   177  	}
   178  
   179  	dummyDataJSON, _ := json.Marshal(dummyData)
   180  
   181  	testCases := []struct {
   182  		description   string
   183  		prevStateJSON string
   184  		expected      *apppb.AppPassword
   185  		expectedState map[string]map[string]*apppb.AppPassword
   186  	}{
   187  		{
   188  			description:   "GenerateAppPassword with empty state",
   189  			prevStateJSON: `{}`,
   190  			expected: &apppb.AppPassword{
   191  				Password:   token,
   192  				TokenScope: nil,
   193  				Label:      "label",
   194  				User:       userTest.GetId(),
   195  				Expiration: nil,
   196  				Ctime:      now,
   197  				Utime:      now,
   198  			},
   199  			expectedState: map[string]map[string]*apppb.AppPassword{
   200  				userTest.GetId().String(): {
   201  					string(hashToken1234): {
   202  						Password:   string(hashToken1234),
   203  						TokenScope: nil,
   204  						Label:      "label",
   205  						User:       userTest.GetId(),
   206  						Expiration: nil,
   207  						Ctime:      now,
   208  						Utime:      now,
   209  					},
   210  				},
   211  			},
   212  		},
   213  		{
   214  			description:   "GenerateAppPassword with not empty state",
   215  			prevStateJSON: string(dummyDataJSON),
   216  			expected: &apppb.AppPassword{
   217  				Password:   token,
   218  				TokenScope: nil,
   219  				Label:      "label",
   220  				User:       userTest.GetId(),
   221  				Expiration: nil,
   222  				Ctime:      now,
   223  				Utime:      now,
   224  			},
   225  			expectedState: concatMaps(map[string]map[string]*apppb.AppPassword{
   226  				userTest.GetId().String(): {
   227  					string(hashToken1234): {
   228  						Password:   string(hashToken1234),
   229  						TokenScope: nil,
   230  						Label:      "label",
   231  						User:       userTest.GetId(),
   232  						Expiration: nil,
   233  						Ctime:      now,
   234  						Utime:      now,
   235  					},
   236  				}},
   237  				dummyData),
   238  		},
   239  	}
   240  
   241  	for _, test := range testCases {
   242  		t.Run(test.description, func(t *testing.T) {
   243  			// initialize temp file with `prevStateJSON` content
   244  			tmpFile := createTempFile(t, tempDir, "test.json")
   245  			defer tmpFile.Close()
   246  			fill(t, tmpFile, test.prevStateJSON)
   247  			manager, err := New(map[string]interface{}{
   248  				"file":               tmpFile.Name(),
   249  				"token_strength":     len(token),
   250  				"password_hash_cost": 11,
   251  			})
   252  			if err != nil {
   253  				t.Fatal("error creating manager:", err)
   254  			}
   255  
   256  			pw, err := manager.GenerateAppPassword(ctx, nil, "label", nil)
   257  			if err != nil {
   258  				t.Fatal("error generating password:", err)
   259  			}
   260  
   261  			// test state in memory
   262  
   263  			if !cmp.Equal(pw, test.expected, protocmp.Transform()) {
   264  				t.Fatalf("apppassword differ: expected=%v got=%v", test.expected, pw)
   265  			}
   266  
   267  			comparePasswords(t, manager.(*jsonManager).passwords, test.expectedState)
   268  
   269  			// test saved json
   270  
   271  			_, err = tmpFile.Seek(0, 0)
   272  			if err != nil {
   273  				t.Fatal(err)
   274  			}
   275  			data, err := io.ReadAll(tmpFile)
   276  			if err != nil {
   277  				t.Fatalf("error reading file %s: %v", tmpFile.Name(), err)
   278  			}
   279  
   280  			var jsonState map[string]map[string]*apppb.AppPassword
   281  			err = json.Unmarshal(data, &jsonState)
   282  			if err != nil {
   283  				t.Fatalf("error decoding json: %v", err)
   284  			}
   285  
   286  			comparePasswords(t, jsonState, test.expectedState)
   287  		})
   288  	}
   289  
   290  }
   291  
   292  func TestListAppPasswords(t *testing.T) {
   293  	user0Test := &userpb.User{Id: &userpb.UserId{Idp: "0"}}
   294  	user1Test := &userpb.User{Id: &userpb.UserId{Idp: "1"}}
   295  	ctx := ctxpkg.ContextSetUser(context.Background(), user0Test)
   296  	tempDir := createTempDir(t, "jsonappauth_test")
   297  	defer os.RemoveAll(tempDir)
   298  
   299  	nowFixed := time.Date(2021, time.May, 21, 12, 21, 0, 0, time.UTC)
   300  	patchNow := monkey.Patch(time.Now, func() time.Time { return nowFixed })
   301  	defer patchNow.Unpatch()
   302  	now := now()
   303  
   304  	token := "hash:1234"
   305  
   306  	dummyDataUser0 := map[string]map[string]*apppb.AppPassword{
   307  		user0Test.GetId().String(): {
   308  			token: {
   309  				Password:   token,
   310  				TokenScope: nil,
   311  				Label:      "label",
   312  				User:       user0Test.GetId(),
   313  				Expiration: nil,
   314  				Ctime:      now,
   315  				Utime:      now,
   316  			},
   317  		}}
   318  
   319  	dummyDataUserExpired := map[string]map[string]*apppb.AppPassword{
   320  		user0Test.GetId().String(): {
   321  			token: {
   322  				Password:   token,
   323  				TokenScope: nil,
   324  				Label:      "label",
   325  				User:       user0Test.GetId(),
   326  				Expiration: &typespb.Timestamp{
   327  					Seconds: 100,
   328  				},
   329  				Ctime: now,
   330  				Utime: now,
   331  			},
   332  		}}
   333  
   334  	dummyDataUser0JSON, _ := json.Marshal(dummyDataUser0)
   335  	dummyDataUserExpiredJSON, _ := json.Marshal(dummyDataUserExpired)
   336  
   337  	dummyDataUser1 := map[string]map[string]*apppb.AppPassword{
   338  		user1Test.GetId().String(): {
   339  			"XXXX": {
   340  				Password:   "XXXX",
   341  				TokenScope: nil,
   342  				Label:      "label",
   343  				User:       user1Test.GetId(),
   344  				Expiration: nil,
   345  				Ctime:      now,
   346  				Utime:      now,
   347  			},
   348  		}}
   349  
   350  	dummyDataTwoUsersJSON, _ := json.Marshal(concatMaps(dummyDataUser0, dummyDataUser1))
   351  
   352  	testCases := []struct {
   353  		description   string
   354  		stateJSON     string
   355  		expectedState []*apppb.AppPassword
   356  	}{
   357  		{
   358  			description:   "ListAppPasswords with empty state",
   359  			stateJSON:     `{}`,
   360  			expectedState: make([]*apppb.AppPassword, 0),
   361  		},
   362  		{
   363  			description:   "ListAppPasswords with not json state file",
   364  			stateJSON:     "",
   365  			expectedState: make([]*apppb.AppPassword, 0),
   366  		},
   367  		{
   368  			description: "ListAppPasswords with not empty state (only one user)",
   369  			stateJSON:   string(dummyDataUser0JSON),
   370  			expectedState: []*apppb.AppPassword{
   371  				dummyDataUser0[user0Test.GetId().String()][token],
   372  			},
   373  		},
   374  		{
   375  			description: "ListAppPasswords with not empty state with expired password (only one user)",
   376  			stateJSON:   string(dummyDataUserExpiredJSON),
   377  			expectedState: []*apppb.AppPassword{
   378  				dummyDataUserExpired[user0Test.GetId().String()][token],
   379  			},
   380  		},
   381  		{
   382  			description: "ListAppPasswords with not empty state (different users)",
   383  			stateJSON:   string(dummyDataTwoUsersJSON),
   384  			expectedState: []*apppb.AppPassword{
   385  				dummyDataUser0[user0Test.GetId().String()][token],
   386  			},
   387  		},
   388  	}
   389  
   390  	for _, test := range testCases {
   391  		t.Run(test.description, func(t *testing.T) {
   392  			// initialize temp file with `state_json` content
   393  			tmpFile := createTempFile(t, tempDir, "test.json")
   394  			defer tmpFile.Close()
   395  			if test.stateJSON != "" {
   396  				fill(t, tmpFile, test.stateJSON)
   397  			}
   398  			manager, err := New(map[string]interface{}{
   399  				"file":           tmpFile.Name(),
   400  				"token_strength": len(token),
   401  			})
   402  			if err != nil {
   403  				t.Fatal("error creating manager:", err)
   404  			}
   405  
   406  			pws, err := manager.ListAppPasswords(ctx)
   407  			if err != nil {
   408  				t.Fatal("error listing passwords:", err)
   409  			}
   410  
   411  			if len(pws) != len(test.expectedState) {
   412  				t.Fatalf("list passwords differ: expected=%v got=%v", test.expectedState, pws)
   413  			}
   414  
   415  			cmp.Equal(pws, test.expectedState, protocmp.Transform())
   416  		})
   417  	}
   418  
   419  }
   420  
   421  func TestInvalidateAppPassword(t *testing.T) {
   422  	userTest := &userpb.User{Id: &userpb.UserId{Idp: "0"}}
   423  	ctx := ctxpkg.ContextSetUser(context.Background(), userTest)
   424  	tempDir := createTempDir(t, "jsonappauth_test")
   425  	defer os.RemoveAll(tempDir)
   426  
   427  	nowFixed := time.Date(2021, time.May, 21, 12, 21, 0, 0, time.UTC)
   428  	patchNow := monkey.Patch(time.Now, func() time.Time { return nowFixed })
   429  	now := now()
   430  	defer patchNow.Unpatch()
   431  
   432  	token := "hash:1234"
   433  
   434  	dummyDataUser1Token := map[string]map[string]*apppb.AppPassword{
   435  		userTest.GetId().String(): {
   436  			token: {
   437  				Password:   token,
   438  				TokenScope: nil,
   439  				Label:      "label",
   440  				User:       userTest.GetId(),
   441  				Expiration: nil,
   442  				Ctime:      now,
   443  				Utime:      now,
   444  			},
   445  		}}
   446  
   447  	dummyDataUser1TokenJSON, _ := json.Marshal(dummyDataUser1Token)
   448  
   449  	dummyDataUser2Token := map[string]map[string]*apppb.AppPassword{
   450  		userTest.GetId().String(): {
   451  			token: {
   452  				Password:   token,
   453  				TokenScope: nil,
   454  				Label:      "label",
   455  				User:       userTest.GetId(),
   456  				Expiration: nil,
   457  				Ctime:      now,
   458  				Utime:      now,
   459  			},
   460  			"hash:XXXX": {
   461  				Password:   "hash:XXXX",
   462  				TokenScope: nil,
   463  				Label:      "label",
   464  				User:       userTest.GetId(),
   465  				Expiration: nil,
   466  				Ctime:      now,
   467  				Utime:      now,
   468  			},
   469  		}}
   470  
   471  	dummyDataUser2TokenJSON, _ := json.Marshal(dummyDataUser2Token)
   472  
   473  	testCases := []struct {
   474  		description   string
   475  		stateJSON     string
   476  		password      string
   477  		expectedState map[string]map[string]*apppb.AppPassword
   478  	}{
   479  		{
   480  			description:   "InvalidateAppPassword with empty state",
   481  			stateJSON:     `{}`,
   482  			password:      "TOKEN_NOT_EXISTS",
   483  			expectedState: nil,
   484  		},
   485  		{
   486  			description:   "InvalidateAppPassword with not empty state and token does not exist",
   487  			stateJSON:     string(dummyDataUser1TokenJSON),
   488  			password:      "TOKEN_NOT_EXISTS",
   489  			expectedState: nil,
   490  		},
   491  		{
   492  			description:   "InvalidateAppPassword with not empty state and token exists",
   493  			stateJSON:     string(dummyDataUser1TokenJSON),
   494  			password:      token,
   495  			expectedState: map[string]map[string]*apppb.AppPassword{},
   496  		},
   497  		{
   498  			description: "InvalidateAppPassword with user that has more than 1 token",
   499  			stateJSON:   string(dummyDataUser2TokenJSON),
   500  			password:    token,
   501  			expectedState: map[string]map[string]*apppb.AppPassword{
   502  				userTest.GetId().String(): {
   503  					"hash:XXXX": {
   504  						Password:   "hash:XXXX",
   505  						TokenScope: nil,
   506  						Label:      "label",
   507  						User:       userTest.GetId(),
   508  						Expiration: nil,
   509  						Ctime:      now,
   510  						Utime:      now,
   511  					},
   512  				},
   513  			},
   514  		},
   515  	}
   516  
   517  	for _, test := range testCases {
   518  		t.Run(test.description, func(t *testing.T) {
   519  			// initialize temp file with `state_json` content
   520  			tmpFile := createTempFile(t, tempDir, "test.json")
   521  			fill(t, tmpFile, test.stateJSON)
   522  			manager, err := New(map[string]interface{}{
   523  				"file":           tmpFile.Name(),
   524  				"token_strength": 4,
   525  			})
   526  			if err != nil {
   527  				t.Fatal("error creating manager:", err)
   528  			}
   529  
   530  			err = manager.InvalidateAppPassword(ctx, test.password)
   531  			if test.expectedState == nil {
   532  				if err == nil {
   533  					t.Fatalf("no error (but we expected one) while get manager")
   534  				} else {
   535  					t.Skip()
   536  				}
   537  			}
   538  			comparePasswords(t, test.expectedState, manager.(*jsonManager).passwords)
   539  		})
   540  	}
   541  
   542  }
   543  
   544  func TestGetAppPassword(t *testing.T) {
   545  	userTest := &userpb.User{Id: &userpb.UserId{Idp: "0"}}
   546  	ctx := ctxpkg.ContextSetUser(context.Background(), userTest)
   547  	tempDir := createTempDir(t, "jsonappauth_test")
   548  	defer os.RemoveAll(tempDir)
   549  
   550  	nowFixed := time.Date(2021, time.May, 21, 12, 21, 0, 0, time.UTC)
   551  	patchNow := monkey.Patch(time.Now, func() time.Time { return nowFixed })
   552  	defer patchNow.Unpatch()
   553  
   554  	now := now()
   555  	token := "1234"
   556  
   557  	generateFromPassword := monkey.Patch(bcrypt.GenerateFromPassword, func(pw []byte, n int) ([]byte, error) {
   558  		return append([]byte("hash:"), pw...), nil
   559  	})
   560  	compareHashAndPassword := monkey.Patch(bcrypt.CompareHashAndPassword, func(hash, pw []byte) error {
   561  		hashPw, _ := bcrypt.GenerateFromPassword(pw, 0)
   562  		if bytes.Equal(hashPw, hash) {
   563  			return nil
   564  		}
   565  		return bcrypt.ErrMismatchedHashAndPassword
   566  	})
   567  	defer generateFromPassword.Restore()
   568  	defer compareHashAndPassword.Restore()
   569  	hashToken1234, _ := bcrypt.GenerateFromPassword([]byte(token), 11)
   570  
   571  	dummyDataUser1Token := map[string]map[string]*apppb.AppPassword{
   572  		userTest.GetId().String(): {
   573  			string(hashToken1234): {
   574  				Password:   string(hashToken1234),
   575  				TokenScope: nil,
   576  				Label:      "label",
   577  				User:       userTest.GetId(),
   578  				Expiration: nil,
   579  				Ctime:      now,
   580  				Utime:      now,
   581  			},
   582  		}}
   583  
   584  	dummyDataUserExpired := map[string]map[string]*apppb.AppPassword{
   585  		userTest.GetId().String(): {
   586  			string(hashToken1234): {
   587  				Password:   string(hashToken1234),
   588  				TokenScope: nil,
   589  				Label:      "label",
   590  				User:       userTest.GetId(),
   591  				Expiration: &typespb.Timestamp{
   592  					Seconds: 100,
   593  				},
   594  				Ctime: now,
   595  				Utime: now,
   596  			},
   597  		}}
   598  
   599  	dummyDataUserFutureExpiration := map[string]map[string]*apppb.AppPassword{
   600  		userTest.GetId().String(): {
   601  			string(hashToken1234): {
   602  				Password:   string(hashToken1234),
   603  				TokenScope: nil,
   604  				Label:      "label",
   605  				User:       userTest.GetId(),
   606  				Expiration: &typespb.Timestamp{
   607  					Seconds: uint64(time.Now().Unix()) + 3600,
   608  				},
   609  				Ctime: now,
   610  				Utime: now,
   611  			},
   612  		}}
   613  
   614  	dummyDataUser1TokenJSON, _ := json.Marshal(dummyDataUser1Token)
   615  	dummyDataUserExpiredJSON, _ := json.Marshal(dummyDataUserExpired)
   616  	dummyDataUserFutureExpirationJSON, _ := json.Marshal(dummyDataUserFutureExpiration)
   617  
   618  	dummyDataDifferentUserToken := map[string]map[string]*apppb.AppPassword{
   619  		"OTHER_USER_ID": {
   620  			string(hashToken1234): {
   621  				Password:   string(hashToken1234),
   622  				TokenScope: nil,
   623  				Label:      "label",
   624  				User:       &userpb.UserId{Idp: "OTHER_USER_ID"},
   625  				Expiration: nil,
   626  				Ctime:      now,
   627  				Utime:      now,
   628  			},
   629  		}}
   630  
   631  	dummyDataDifferentUserTokenJSON, _ := json.Marshal(dummyDataDifferentUserToken)
   632  
   633  	testCases := []struct {
   634  		description   string
   635  		stateJSON     string
   636  		password      string
   637  		expectedState *apppb.AppPassword
   638  	}{
   639  		{
   640  			description:   "GetAppPassword with token that does not exist",
   641  			stateJSON:     string(dummyDataUser1TokenJSON),
   642  			password:      "TOKEN_NOT_EXISTS",
   643  			expectedState: nil,
   644  		},
   645  		{
   646  			description:   "GetAppPassword with expired token",
   647  			stateJSON:     string(dummyDataUserExpiredJSON),
   648  			password:      "1234",
   649  			expectedState: nil,
   650  		},
   651  		{
   652  			description:   "GetAppPassword with token with expiration set in the future",
   653  			stateJSON:     string(dummyDataUserFutureExpirationJSON),
   654  			password:      "1234",
   655  			expectedState: dummyDataUserFutureExpiration[userTest.GetId().String()][string(hashToken1234)],
   656  		},
   657  		{
   658  			description:   "GetAppPassword with token that exists but different user",
   659  			stateJSON:     string(dummyDataDifferentUserTokenJSON),
   660  			password:      "1234",
   661  			expectedState: nil,
   662  		},
   663  		{
   664  			description:   "GetAppPassword with token that exists owned by user",
   665  			stateJSON:     string(dummyDataUser1TokenJSON),
   666  			password:      "1234",
   667  			expectedState: dummyDataUser1Token[userTest.GetId().String()][string(hashToken1234)],
   668  		},
   669  	}
   670  
   671  	for _, test := range testCases {
   672  		t.Run(test.description, func(t *testing.T) {
   673  			// initialize temp file with `state_json` content
   674  			tmpFile := createTempFile(t, tempDir, "test.json")
   675  			fill(t, tmpFile, test.stateJSON)
   676  			manager, err := New(map[string]interface{}{
   677  				"file":           tmpFile.Name(),
   678  				"token_strength": 4,
   679  			})
   680  			if err != nil {
   681  				t.Fatal("error creating manager:", err)
   682  			}
   683  
   684  			pw, err := manager.GetAppPassword(ctx, userTest.GetId(), test.password)
   685  			if test.expectedState == nil {
   686  				if err == nil {
   687  					t.Fatalf("no error (but we expected one) while get manager")
   688  				} else {
   689  					t.Skip()
   690  				}
   691  			}
   692  			if !cmp.Equal(test.expectedState, pw, protocmp.Transform()) {
   693  				t.Fatalf("apppauth state differ: expected=%v got=%v", test.expectedState, pw)
   694  			}
   695  
   696  		})
   697  	}
   698  }
   699  
   700  func createTempDir(t *testing.T, name string) string {
   701  	tempDir, err := os.MkdirTemp("", name)
   702  	if err != nil {
   703  		t.Fatalf("error while creating temp dir: %v", err)
   704  	}
   705  	return tempDir
   706  }
   707  
   708  func createTempFile(t *testing.T, tempDir string, name string) *os.File {
   709  	tempFile, err := os.CreateTemp(tempDir, name)
   710  	if err != nil {
   711  		t.Fatalf("error while creating temp file: %v", err)
   712  	}
   713  	return tempFile
   714  }
   715  
   716  func fill(t *testing.T, file *os.File, data string) {
   717  	_, err := file.WriteString(data)
   718  	if err != nil {
   719  		t.Fatalf("error while writing to file: %v", err)
   720  	}
   721  }
   722  
   723  func concatMaps(maps ...map[string]map[string]*apppb.AppPassword) map[string]map[string]*apppb.AppPassword {
   724  	res := make(map[string]map[string]*apppb.AppPassword)
   725  	for _, m := range maps {
   726  		for k := range m {
   727  			res[k] = m[k]
   728  		}
   729  	}
   730  	return res
   731  }
   732  
   733  func comparePasswords(t *testing.T, expected, got map[string]map[string]*apppb.AppPassword) {
   734  	if !cmp.Equal(expected, got, protocmp.Transform()) {
   735  		t.Fatalf("passwords differ: expected=%v got=%v", expected, got)
   736  	}
   737  }