package proxy import ( "encoding/base64" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "testing" "time" "github.com/pomerium/pomerium/internal/config" "github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/proxy/clients" ) type mockCipher struct{} func (a mockCipher) Encrypt(s []byte) ([]byte, error) { if string(s) == "error" { return []byte(""), errors.New("error encrypting") } return []byte("OK"), nil } func (a mockCipher) Decrypt(s []byte) ([]byte, error) { if string(s) == "error" { return []byte(""), errors.New("error encrypting") } return []byte("OK"), nil } func (a mockCipher) Marshal(s interface{}) (string, error) { return "ok", nil } func (a mockCipher) Unmarshal(s string, i interface{}) error { if string(s) == "unmarshal error" || string(s) == "error" { return errors.New("error") } return nil } func TestProxy_RobotsTxt(t *testing.T) { proxy := Proxy{} req := httptest.NewRequest("GET", "/robots.txt", nil) rr := httptest.NewRecorder() proxy.RobotsTxt(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) } expected := fmt.Sprintf("User-agent: *\nDisallow: /") if rr.Body.String() != expected { t.Errorf("handler returned wrong body: got %v want %v", rr.Body.String(), expected) } } func TestProxy_GetRedirectURL(t *testing.T) { tests := []struct { name string host string want *url.URL }{ {"google", "google.com", &url.URL{Scheme: "https", Host: "google.com", Path: "/.pomerium/callback"}}, {"pomerium", "pomerium.io", &url.URL{Scheme: "https", Host: "pomerium.io", Path: "/.pomerium/callback"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Proxy{redirectURL: &url.URL{Path: "/.pomerium/callback"}} if got := p.GetRedirectURL(tt.host); !reflect.DeepEqual(got, tt.want) { t.Errorf("Proxy.GetRedirectURL() = %v, want %v", got, tt.want) } }) } } func TestProxy_signRedirectURL(t *testing.T) { tests := []struct { name string rawRedirect string timestamp time.Time want string }{ {"pomerium", "https://pomerium.io/.pomerium/callback", fixedDate, "wq3rAjRGN96RXS8TAzH-uxQTD0XgY_8ZYEKMiOLD5P4="}, {"google", "https://google.com/.pomerium/callback", fixedDate, "7EYHZObq167CuyuPm5CqOtkU4zg5dFeUCs7W7QOrgNQ="}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Proxy{} if got := p.signRedirectURL(tt.rawRedirect, tt.timestamp); got != tt.want { t.Errorf("Proxy.signRedirectURL() = %v, want %v", got, tt.want) } }) } } func TestProxy_GetSignOutURL(t *testing.T) { tests := []struct { name string authenticate string redirect string wantPrefix string }{ {"without scheme", "auth.corp.pomerium.io", "hello.corp.pomerium.io", "https://auth.corp.pomerium.io/sign_out?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io"}, {"with scheme", "https://auth.corp.pomerium.io", "https://hello.corp.pomerium.io", "https://auth.corp.pomerium.io/sign_out?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { authenticateURL, _ := urlParse(tt.authenticate) redirectURL, _ := urlParse(tt.redirect) p := &Proxy{} // signature is ignored as it is tested above. Avoids testing time.Now if got := p.GetSignOutURL(authenticateURL, redirectURL); !strings.HasPrefix(got.String(), tt.wantPrefix) { t.Errorf("Proxy.GetSignOutURL() = %v, wantPrefix %v", got.String(), tt.wantPrefix) } }) } } func TestProxy_GetSignInURL(t *testing.T) { tests := []struct { name string authenticate string redirect string state string wantPrefix string }{ {"without scheme", "auth.corp.pomerium.io", "hello.corp.pomerium.io", "example_state", "https://auth.corp.pomerium.io/sign_in?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io&response_type=code&shared_secret=shared-secret"}, {"with scheme", "https://auth.corp.pomerium.io", "https://hello.corp.pomerium.io", "example_state", "https://auth.corp.pomerium.io/sign_in?redirect_uri=https%3A%2F%2Fhello.corp.pomerium.io&response_type=code&shared_secret=shared-secret"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Proxy{SharedKey: "shared-secret"} authenticateURL, _ := urlParse(tt.authenticate) redirectURL, _ := urlParse(tt.redirect) if got := p.GetSignInURL(authenticateURL, redirectURL, tt.state); !strings.HasPrefix(got.String(), tt.wantPrefix) { t.Errorf("Proxy.GetSignOutURL() = %v, wantPrefix %v", got.String(), tt.wantPrefix) } }) } } func TestProxy_Signout(t *testing.T) { proxy, err := New(testOptions()) if err != nil { t.Fatal(err) } req := httptest.NewRequest("GET", "/.pomerium/sign_out", nil) rr := httptest.NewRecorder() proxy.SignOut(rr, req) if status := rr.Code; status != http.StatusFound { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusFound) } // todo(bdd) : good way of mocking auth then serving a simple favicon? } func TestProxy_OAuthStart(t *testing.T) { proxy, err := New(testOptions()) if err != nil { t.Fatal(err) } req := httptest.NewRequest("GET", "/oauth-start", nil) rr := httptest.NewRecorder() proxy.OAuthStart(rr, req) // expect oauth redirect if status := rr.Code; status != http.StatusFound { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusFound) } // expected url expected := `