mirror of
				https://gogs.blitter.com/RLabs/xs
				synced 2024-08-14 10:26:42 +00:00 
			
		
		
		
	auth.go: AuthCtx added to structure mockable entities for testing
Signed-off-by: Russ Magee <rmagee@gmail.com>
This commit is contained in:
		
							parent
							
								
									f372666187
								
							
						
					
					
						commit
						5bd3ee0e4c
					
				
					 3 changed files with 56 additions and 29 deletions
				
			
		
							
								
								
									
										47
									
								
								auth.go
									
										
									
									
									
								
							
							
						
						
									
										47
									
								
								auth.go
									
										
									
									
									
								
							|  | @ -26,15 +26,25 @@ import ( | ||||||
| 	passlib "gopkg.in/hlandau/passlib.v1" | 	passlib "gopkg.in/hlandau/passlib.v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type AuthCtx struct { | ||||||
|  | 	reader     func(string) ([]byte, error)     // eg. ioutil.ReadFile() | ||||||
|  | 	userlookup func(string) (*user.User, error) // eg. os/user.Lookup() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewAuthCtx(/*reader func(string) ([]byte, error), userlookup func(string) (*user.User, error)*/) (ret *AuthCtx) { | ||||||
|  | 		ret = &AuthCtx{ioutil.ReadFile, user.Lookup} | ||||||
|  | 		return | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // --------- System passwd/shadow auth routine(s) -------------- | // --------- System passwd/shadow auth routine(s) -------------- | ||||||
| // Verify a password against system standard shadow file | // Verify a password against system standard shadow file | ||||||
| // Note auxilliary fields for expiry policy are *not* inspected. | // Note auxilliary fields for expiry policy are *not* inspected. | ||||||
| func VerifyPass(reader func(string) ([]byte, error), user, password string) (bool, error) { | func VerifyPass(ctx *AuthCtx, user, password string) (bool, error) { | ||||||
| 	if reader == nil { | 	if ctx.reader == nil { | ||||||
| 		reader = ioutil.ReadFile // dependency injection hides that this is required | 		ctx.reader = ioutil.ReadFile // dependency injection hides that this is required | ||||||
| 	} | 	} | ||||||
| 	passlib.UseDefaults(passlib.Defaults20180601) | 	passlib.UseDefaults(passlib.Defaults20180601) | ||||||
| 	pwFileData, e := reader("/etc/shadow") | 	pwFileData, e := ctx.reader("/etc/shadow") | ||||||
| 	if e != nil { | 	if e != nil { | ||||||
| 		return false, e | 		return false, e | ||||||
| 	} | 	} | ||||||
|  | @ -73,11 +83,14 @@ func VerifyPass(reader func(string) ([]byte, error), user, password string) (boo | ||||||
| // This checks /etc/xs.passwd for auth info, and system /etc/passwd | // This checks /etc/xs.passwd for auth info, and system /etc/passwd | ||||||
| // to cross-check the user actually exists. | // to cross-check the user actually exists. | ||||||
| // nolint: gocyclo | // nolint: gocyclo | ||||||
| func AuthUserByPasswd(reader func(string) ([]byte, error), userlookup func(string) (*user.User, error), username string, auth string, fname string) (valid bool, allowedCmds string) { | func AuthUserByPasswd(ctx *AuthCtx, username string, auth string, fname string) (valid bool, allowedCmds string) { | ||||||
| 	if reader == nil { | 	if ctx.reader == nil { | ||||||
| 		reader = ioutil.ReadFile // dependency injection hides that this is required | 		ctx.reader = ioutil.ReadFile // dependency injection hides that this is required | ||||||
| 	} | 	} | ||||||
| 	b, e := reader(fname) // nolint: gosec | 	if ctx.userlookup == nil { | ||||||
|  | 		ctx.userlookup = user.Lookup // again for dependency injection as dep is now hidden | ||||||
|  | 	} | ||||||
|  | 	b, e := ctx.reader(fname) // nolint: gosec | ||||||
| 	if e != nil { | 	if e != nil { | ||||||
| 		valid = false | 		valid = false | ||||||
| 		log.Printf("ERROR: Cannot read %s!\n", fname) | 		log.Printf("ERROR: Cannot read %s!\n", fname) | ||||||
|  | @ -121,7 +134,7 @@ func AuthUserByPasswd(reader func(string) ([]byte, error), userlookup func(strin | ||||||
| 	r = nil | 	r = nil | ||||||
| 	runtime.GC() | 	runtime.GC() | ||||||
| 
 | 
 | ||||||
| 	_, userErr := userlookup(username) | 	_, userErr := ctx.userlookup(username) | ||||||
| 	if userErr != nil { | 	if userErr != nil { | ||||||
| 		valid = false | 		valid = false | ||||||
| 	} | 	} | ||||||
|  | @ -135,21 +148,21 @@ func AuthUserByPasswd(reader func(string) ([]byte, error), userlookup func(strin | ||||||
| // via the -g option. | // via the -g option. | ||||||
| // The function also check system /etc/passwd to cross-check the user | // The function also check system /etc/passwd to cross-check the user | ||||||
| // actually exists. | // actually exists. | ||||||
| func AuthUserByToken(reader func(string) ([]byte, error), userlookup func(string) (*user.User, error), username string, connhostname string, auth string) (valid bool) { | func AuthUserByToken(ctx *AuthCtx, username string, connhostname string, auth string) (valid bool) { | ||||||
| 	if reader == nil { | 	if ctx.reader == nil { | ||||||
| 		reader = ioutil.ReadFile // dependency injection hides that this is required | 		ctx.reader = ioutil.ReadFile // dependency injection hides that this is required | ||||||
| 	} | 	} | ||||||
| 	if userlookup == nil { | 	if ctx.userlookup == nil { | ||||||
| 		userlookup = user.Lookup // again for dependency injection as dep is now hidden | 		ctx.userlookup = user.Lookup // again for dependency injection as dep is now hidden | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auth = strings.TrimSpace(auth) | 	auth = strings.TrimSpace(auth) | ||||||
| 	u, ue := userlookup(username) | 	u, ue := ctx.userlookup(username) | ||||||
| 	if ue != nil { | 	if ue != nil { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	b, e := reader(fmt.Sprintf("%s/.xs_id", u.HomeDir)) | 	b, e := ctx.reader(fmt.Sprintf("%s/.xs_id", u.HomeDir)) | ||||||
| 	if e != nil { | 	if e != nil { | ||||||
| 		log.Printf("INFO: Cannot read %s/.xs_id\n", u.HomeDir) | 		log.Printf("INFO: Cannot read %s/.xs_id\n", u.HomeDir) | ||||||
| 		return false | 		return false | ||||||
|  | @ -176,7 +189,7 @@ func AuthUserByToken(reader func(string) ([]byte, error), userlookup func(string | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	_, userErr := userlookup(username) | 	_, userErr := ctx.userlookup(username) | ||||||
| 	if userErr != nil { | 	if userErr != nil { | ||||||
| 		valid = false | 		valid = false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								auth_test.go
									
										
									
									
									
								
							
							
						
						
									
										32
									
								
								auth_test.go
									
										
									
									
									
								
							|  | @ -31,6 +31,11 @@ disableduser:!:18310::::::` | ||||||
| 	readfile_arg_f   string | 	readfile_arg_f   string | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | func newMockAuthCtx(reader func(string) ([]byte, error), userlookup func(string) (*user.User, error)) (ret *AuthCtx) { | ||||||
|  | 		ret = &AuthCtx{reader, userlookup} | ||||||
|  | 		return | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func _mock_user_Lookup(username string) (*user.User, error) { | func _mock_user_Lookup(username string) (*user.User, error) { | ||||||
| 	username = userlookup_arg_u | 	username = userlookup_arg_u | ||||||
| 	if username == "baduser" { | 	if username == "baduser" { | ||||||
|  | @ -64,8 +69,9 @@ func _mock_ioutil_ReadFileHasError(f string) ([]byte, error) { | ||||||
| 
 | 
 | ||||||
| func TestVerifyPass(t *testing.T) { | func TestVerifyPass(t *testing.T) { | ||||||
| 	readfile_arg_f = "/etc/shadow" | 	readfile_arg_f = "/etc/shadow" | ||||||
|  | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFile, nil) | ||||||
| 	for idx, rec := range testGoodUsers { | 	for idx, rec := range testGoodUsers { | ||||||
| 		stat, e := VerifyPass(_mock_ioutil_ReadFile, rec.user, rec.passwd) | 		stat, e := VerifyPass(ctx, rec.user, rec.passwd) | ||||||
| 		if rec.good && (!stat || e != nil) { | 		if rec.good && (!stat || e != nil) { | ||||||
| 			t.Fatalf("failed %d\n", idx) | 			t.Fatalf("failed %d\n", idx) | ||||||
| 		} | 		} | ||||||
|  | @ -73,21 +79,24 @@ func TestVerifyPass(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestVerifyPassFailsOnEmptyFile(t *testing.T) { | func TestVerifyPassFailsOnEmptyFile(t *testing.T) { | ||||||
| 	stat, e := VerifyPass(_mock_ioutil_ReadFileEmpty, "johndoe", "sompass") | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, nil) | ||||||
|  | 	stat, e := VerifyPass(ctx, "johndoe", "somepass") | ||||||
| 	if stat || (e == nil) { | 	if stat || (e == nil) { | ||||||
| 		t.Fatal("failed to fail w/empty file") | 		t.Fatal("failed to fail w/empty file") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestVerifyPassFailsOnFileError(t *testing.T) { | func TestVerifyPassFailsOnFileError(t *testing.T) { | ||||||
| 	stat, e := VerifyPass(_mock_ioutil_ReadFileEmpty, "johndoe", "somepass") | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, nil) | ||||||
|  | 	stat, e := VerifyPass(ctx, "johndoe", "somepass") | ||||||
| 	if stat || (e == nil) { | 	if stat || (e == nil) { | ||||||
| 		t.Fatal("failed to fail on ioutil.ReadFile error") | 		t.Fatal("failed to fail on ioutil.ReadFile error") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestVerifyPassFailsOnDisabledEntry(t *testing.T) { | func TestVerifyPassFailsOnDisabledEntry(t *testing.T) { | ||||||
| 	stat, e := VerifyPass(_mock_ioutil_ReadFileEmpty, "disableduser", "!") | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, nil) | ||||||
|  | 	stat, e := VerifyPass(ctx, "disableduser", "!") | ||||||
| 	if stat || (e == nil) { | 	if stat || (e == nil) { | ||||||
| 		t.Fatal("failed to fail on disabled user entry") | 		t.Fatal("failed to fail on disabled user entry") | ||||||
| 	} | 	} | ||||||
|  | @ -96,38 +105,43 @@ func TestVerifyPassFailsOnDisabledEntry(t *testing.T) { | ||||||
| //// | //// | ||||||
| 
 | 
 | ||||||
| func TestAuthUserByTokenFailsOnMissingEntryForHost(t *testing.T) { | func TestAuthUserByTokenFailsOnMissingEntryForHost(t *testing.T) { | ||||||
| 	stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "johndoe", "hostZ", "abcdefg") | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup) | ||||||
|  | 	stat := AuthUserByToken(ctx, "johndoe", "hostZ", "abcdefg") | ||||||
| 	if stat { | 	if stat { | ||||||
| 		t.Fatal("failed to fail on missing/mismatched host entry") | 		t.Fatal("failed to fail on missing/mismatched host entry") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAuthUserByTokenFailsOnMissingEntryForUser(t *testing.T) { | func TestAuthUserByTokenFailsOnMissingEntryForUser(t *testing.T) { | ||||||
| 	stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "unkuser", "hostA", "abcdefg") | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup) | ||||||
|  | 	stat := AuthUserByToken(ctx, "unkuser", "hostA", "abcdefg") | ||||||
| 	if stat { | 	if stat { | ||||||
| 		t.Fatal("failed to fail on wrong user") | 		t.Fatal("failed to fail on wrong user") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAuthUserByTokenFailsOnUserLookupFailure(t *testing.T) { | func TestAuthUserByTokenFailsOnUserLookupFailure(t *testing.T) { | ||||||
|  | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup) | ||||||
| 	userlookup_arg_u = "baduser" | 	userlookup_arg_u = "baduser" | ||||||
| 	stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "johndoe", "hostA", "abcdefg") | 	stat := AuthUserByToken(ctx, "johndoe", "hostA", "abcdefg") | ||||||
| 	if stat { | 	if stat { | ||||||
| 		t.Fatal("failed to fail with bad return from user.Lookup()") | 		t.Fatal("failed to fail with bad return from user.Lookup()") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAuthUserByTokenFailsOnMismatchedTokenForUser(t *testing.T) { | func TestAuthUserByTokenFailsOnMismatchedTokenForUser(t *testing.T) { | ||||||
| 	stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "johndoe", "hostA", "badtoken") | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup) | ||||||
|  | 	stat := AuthUserByToken(ctx, "johndoe", "hostA", "badtoken") | ||||||
| 	if stat { | 	if stat { | ||||||
| 		t.Fatal("failed to fail with valid user, bad token") | 		t.Fatal("failed to fail with valid user, bad token") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAuthUserByTokenSucceedsWithMatchedUserAndToken(t *testing.T) { | func TestAuthUserByTokenSucceedsWithMatchedUserAndToken(t *testing.T) { | ||||||
|  | 	ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup) | ||||||
| 	userlookup_arg_u = "johndoe" | 	userlookup_arg_u = "johndoe" | ||||||
| 	readfile_arg_f = "/.xs_id" | 	readfile_arg_f = "/.xs_id" | ||||||
| 	stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, userlookup_arg_u, "hostA", "hostA:abcdefg") | 	stat := AuthUserByToken(ctx, userlookup_arg_u, "hostA", "hostA:abcdefg") | ||||||
| 	if !stat { | 	if !stat { | ||||||
| 		t.Fatal("failed with valid user and token") | 		t.Fatal("failed with valid user and token") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -708,14 +708,14 @@ func main() { | ||||||
| 
 | 
 | ||||||
| 				var valid bool | 				var valid bool | ||||||
| 				var allowedCmds string // Currently unused | 				var allowedCmds string // Currently unused | ||||||
| 				if xs.AuthUserByToken(ioutil.ReadFile, user.Lookup, string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) { | 				if xs.AuthUserByToken(xs.NewAuthCtx(), string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) { | ||||||
| 					valid = true | 					valid = true | ||||||
| 				} else { | 				} else { | ||||||
| 					if useSystemPasswd { | 					if useSystemPasswd { | ||||||
| 						//var passErr error | 						//var passErr error | ||||||
| 						valid, _ /*passErr*/ = xs.VerifyPass(ioutil.ReadFile, string(rec.Who()), string(rec.AuthCookie(true))) | 						valid, _ /*passErr*/ = xs.VerifyPass(xs.NewAuthCtx(), string(rec.Who()), string(rec.AuthCookie(true))) | ||||||
| 					} else { | 					} else { | ||||||
| 						valid, allowedCmds = xs.AuthUserByPasswd(ioutil.ReadFile, user.Lookup, string(rec.Who()), string(rec.AuthCookie(true)), "/etc/xs.passwd") | 						valid, allowedCmds = xs.AuthUserByPasswd(xs.NewAuthCtx(), string(rec.Who()), string(rec.AuthCookie(true)), "/etc/xs.passwd") | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue