github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/githuboauth/githuboauth_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package githuboauth 18 19 import ( 20 "encoding/gob" 21 "encoding/hex" 22 "net/http" 23 "net/http/httptest" 24 "testing" 25 26 "github.com/google/go-github/github" 27 "github.com/gorilla/securecookie" 28 "github.com/gorilla/sessions" 29 "github.com/sirupsen/logrus" 30 "golang.org/x/net/context" 31 "golang.org/x/net/xsrftoken" 32 "golang.org/x/oauth2" 33 34 "k8s.io/test-infra/prow/config" 35 ) 36 37 const mockAccessToken = "justSomeRandomSecretToken" 38 39 type MockOAuthClient struct{} 40 41 func (c *MockOAuthClient) Exchange(ctx context.Context, code string) (*oauth2.Token, error) { 42 return &oauth2.Token{ 43 AccessToken: mockAccessToken, 44 }, nil 45 } 46 47 func (c *MockOAuthClient) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string { 48 return "mock-auth-url" 49 } 50 51 func getMockConfig(cookie *sessions.CookieStore) *config.GithubOAuthConfig { 52 clientId := "mock-client-id" 53 clientSecret := "mock-client-secret" 54 redirectURL := "/uni-test/redirect-url" 55 scopes := []string{} 56 57 return &config.GithubOAuthConfig{ 58 ClientID: clientId, 59 ClientSecret: clientSecret, 60 RedirectURL: redirectURL, 61 Scopes: scopes, 62 FinalRedirectURL: "/unit-test/final-redirect-url", 63 64 CookieStore: cookie, 65 } 66 } 67 68 func createMockStateToken(config *config.GithubOAuthConfig) string { 69 stateToken := xsrftoken.Generate(config.ClientSecret, "", "") 70 state := hex.EncodeToString([]byte(stateToken)) 71 72 return state 73 } 74 75 func isEqual(token1 *oauth2.Token, token2 *oauth2.Token) bool { 76 return token1.AccessToken == token2.AccessToken && 77 token1.Expiry == token2.Expiry && 78 token1.RefreshToken == token2.RefreshToken && 79 token1.TokenType == token2.TokenType 80 } 81 82 func TestHandleLogin(t *testing.T) { 83 cookie := sessions.NewCookieStore([]byte("secret-key")) 84 mockConfig := getMockConfig(cookie) 85 mockLogger := logrus.WithField("uni-test", "githuboauth") 86 mockGithubOAuthAgent := NewGithubOAuthAgent(mockConfig, mockLogger) 87 mockOAuthClient := &MockOAuthClient{} 88 89 mockRequest := httptest.NewRequest(http.MethodGet, "/mock-login", nil) 90 mockResponse := httptest.NewRecorder() 91 92 handleLoginFn := mockGithubOAuthAgent.HandleLogin(mockOAuthClient) 93 handleLoginFn.ServeHTTP(mockResponse, mockRequest) 94 result := mockResponse.Result() 95 if result.StatusCode != http.StatusFound { 96 t.Errorf("Unexpected status code. Got %v, expected %v", result.StatusCode, http.StatusFound) 97 } 98 resultCookies := result.Cookies() 99 var oauthCookie *http.Cookie 100 for _, v := range resultCookies { 101 if v.Name == oauthSessionCookie { 102 oauthCookie = v 103 break 104 } 105 } 106 if oauthCookie == nil { 107 t.Error("Cookie for oauth session not found") 108 } 109 decodedCookie := make(map[interface{}]interface{}) 110 if err := securecookie.DecodeMulti(oauthCookie.Name, oauthCookie.Value, &decodedCookie, cookie.Codecs...); err != nil { 111 t.Fatalf("Cannot decoded cookie: %v", err) 112 } 113 state, ok := decodedCookie[stateKey].(string) 114 if !ok { 115 t.Fatal("Error with getting state parameter") 116 } 117 stateTokenRaw, err := hex.DecodeString(state) 118 if err != nil { 119 t.Fatal("Cannot decoding state token") 120 } 121 stateToken := string(stateTokenRaw) 122 if !xsrftoken.Valid(stateToken, mockConfig.ClientSecret, "", "") { 123 t.Error("Expect the state token is valid, found state token invalid.") 124 } 125 if state == "" { 126 t.Error("Expect state parameter is not empty, found empty") 127 } 128 } 129 130 func TestHandleLogout(t *testing.T) { 131 cookie := sessions.NewCookieStore([]byte("secret-key")) 132 mockConfig := getMockConfig(cookie) 133 mockLogger := logrus.WithField("uni-test", "githuboauth") 134 mockGithubOAuthAgent := NewGithubOAuthAgent(mockConfig, mockLogger) 135 mockOAuthClient := &MockOAuthClient{} 136 137 mockRequest := httptest.NewRequest(http.MethodGet, "/mock-logout", nil) 138 _, err := cookie.New(mockRequest, tokenSession) 139 if err != nil { 140 t.Fatalf("Failed to create a mock token session with error: %v", err) 141 } 142 mockResponse := httptest.NewRecorder() 143 144 handleLoginFn := mockGithubOAuthAgent.HandleLogout(mockOAuthClient) 145 handleLoginFn.ServeHTTP(mockResponse, mockRequest) 146 result := mockResponse.Result() 147 if result.StatusCode != http.StatusFound { 148 t.Errorf("Unexpected status code. Got %v, expected %v", result.StatusCode, http.StatusFound) 149 } 150 resultCookies := result.Cookies() 151 var tokenCookie *http.Cookie 152 cookieCounts := 0 153 for _, v := range resultCookies { 154 if v.Name == tokenSession { 155 tokenCookie = v 156 cookieCounts++ 157 } 158 } 159 if cookieCounts != 1 { 160 t.Errorf("Wrong number of %s cookie. There should be only one cookie with name %s", tokenSession, tokenSession) 161 } 162 if tokenCookie == nil { 163 t.Error("Cookie for oauth session not found") 164 } 165 if tokenCookie.MaxAge != -1 { 166 t.Errorf("Expect cookie MaxAge equals -1, %d", tokenCookie.MaxAge) 167 } 168 } 169 170 func TestHandleLogoutWithLoginSession(t *testing.T) { 171 cookie := sessions.NewCookieStore([]byte("secret-key")) 172 mockConfig := getMockConfig(cookie) 173 mockLogger := logrus.WithField("uni-test", "githuboauth") 174 mockGithubOAuthAgent := NewGithubOAuthAgent(mockConfig, mockLogger) 175 mockOAuthClient := &MockOAuthClient{} 176 177 mockRequest := httptest.NewRequest(http.MethodGet, "/mock-logout", nil) 178 _, err := cookie.New(mockRequest, tokenSession) 179 mocKLoginSession := &http.Cookie{ 180 Name: loginSession, 181 Path: "/", 182 } 183 mockRequest.AddCookie(mocKLoginSession) 184 if err != nil { 185 t.Fatalf("Failed to create a mock token session with error: %v", err) 186 } 187 mockResponse := httptest.NewRecorder() 188 189 handleLoginFn := mockGithubOAuthAgent.HandleLogout(mockOAuthClient) 190 handleLoginFn.ServeHTTP(mockResponse, mockRequest) 191 result := mockResponse.Result() 192 if result.StatusCode != http.StatusFound { 193 t.Errorf("Unexpected status code. Got %v, expected %v", result.StatusCode, http.StatusFound) 194 } 195 resultCookies := result.Cookies() 196 var loginCookie *http.Cookie 197 for _, v := range resultCookies { 198 if v.Name == loginSession { 199 loginCookie = v 200 break 201 } 202 } 203 if loginCookie == nil { 204 t.Error("Cookie for oauth session not found") 205 } 206 if loginCookie.MaxAge != -1 { 207 t.Errorf("Expect cookie MaxAge equals -1, %d", loginCookie.MaxAge) 208 } 209 } 210 211 type fakeGithubClient struct { 212 login string 213 } 214 215 func (fgc *fakeGithubClient) GetUser(login string) (*github.User, error) { 216 return &github.User{ 217 Login: &fgc.login, 218 }, nil 219 } 220 221 type fakeGetter struct { 222 login string 223 } 224 225 func (fgc *fakeGetter) GetGithubClient(accessToken string, dryRun bool) GithubClientWrapper { 226 return &fakeGithubClient{login: fgc.login} 227 } 228 229 func TestHandleRedirectWithInvalidState(t *testing.T) { 230 gob.Register(&oauth2.Token{}) 231 cookie := sessions.NewCookieStore([]byte("secret-key")) 232 mockConfig := getMockConfig(cookie) 233 mockLogger := logrus.WithField("uni-test", "githuboauth") 234 mockGithubOAuthAgent := NewGithubOAuthAgent(mockConfig, mockLogger) 235 mockOAuthClient := &MockOAuthClient{} 236 mockStateToken := createMockStateToken(mockConfig) 237 238 mockRequest := httptest.NewRequest(http.MethodGet, "/mock-login", nil) 239 mockResponse := httptest.NewRecorder() 240 query := mockRequest.URL.Query() 241 query.Add("state", "bad-state-token") 242 mockRequest.URL.RawQuery = query.Encode() 243 mockSession, err := sessions.GetRegistry(mockRequest).Get(cookie, oauthSessionCookie) 244 if err != nil { 245 t.Fatalf("Error with getting mock session: %v", err) 246 } 247 mockSession.Values[stateKey] = mockStateToken 248 249 handleLoginFn := mockGithubOAuthAgent.HandleRedirect(mockOAuthClient, &fakeGetter{""}) 250 handleLoginFn.ServeHTTP(mockResponse, mockRequest) 251 result := mockResponse.Result() 252 253 if result.StatusCode != http.StatusInternalServerError { 254 t.Errorf("Invalid status code. Got %v, expected %v", result.StatusCode, http.StatusInternalServerError) 255 } 256 } 257 258 func TestHandleRedirectWithValidState(t *testing.T) { 259 gob.Register(&oauth2.Token{}) 260 cookie := sessions.NewCookieStore([]byte("secret-key")) 261 mockConfig := getMockConfig(cookie) 262 mockLogger := logrus.WithField("uni-test", "githuboauth") 263 mockGithubOAuthAgent := NewGithubOAuthAgent(mockConfig, mockLogger) 264 mockLogin := "foo_name" 265 mockOAuthClient := &MockOAuthClient{} 266 mockStateToken := createMockStateToken(mockConfig) 267 268 mockRequest := httptest.NewRequest(http.MethodGet, "/mock-login", nil) 269 mockResponse := httptest.NewRecorder() 270 query := mockRequest.URL.Query() 271 query.Add("state", mockStateToken) 272 mockRequest.URL.RawQuery = query.Encode() 273 274 mockSession, err := sessions.GetRegistry(mockRequest).Get(cookie, oauthSessionCookie) 275 if err != nil { 276 t.Fatalf("Error with getting mock session: %v", err) 277 } 278 mockSession.Values[stateKey] = mockStateToken 279 280 handleLoginFn := mockGithubOAuthAgent.HandleRedirect(mockOAuthClient, &fakeGetter{mockLogin}) 281 handleLoginFn.ServeHTTP(mockResponse, mockRequest) 282 result := mockResponse.Result() 283 if result.StatusCode != http.StatusFound { 284 t.Errorf("Invalid status code. Got %v, expected %v", result.StatusCode, http.StatusFound) 285 } 286 resultCookies := result.Cookies() 287 var oauthCookie *http.Cookie 288 for _, v := range resultCookies { 289 if v.Name == tokenSession { 290 oauthCookie = v 291 break 292 } 293 } 294 if oauthCookie == nil { 295 t.Fatalf("Cookie for oauth session not found") 296 } 297 decodedCookie := make(map[interface{}]interface{}) 298 if err := securecookie.DecodeMulti(oauthCookie.Name, oauthCookie.Value, &decodedCookie, cookie.Codecs...); err != nil { 299 t.Fatalf("Cannot decoded cookie: %v", err) 300 } 301 accessTokenFromCookie, ok := decodedCookie[tokenKey].(*oauth2.Token) 302 if !ok { 303 t.Fatalf("Error with getting access token: %v", decodedCookie) 304 } 305 token := &oauth2.Token{ 306 AccessToken: mockAccessToken, 307 } 308 if !isEqual(accessTokenFromCookie, token) { 309 t.Errorf("Invalid access token. Got %v, expected %v", accessTokenFromCookie, token) 310 } 311 var loginCookie *http.Cookie 312 for _, v := range resultCookies { 313 if v.Name == loginSession { 314 loginCookie = v 315 break 316 } 317 } 318 if loginCookie == nil { 319 t.Fatalf("Cookie for github login not found") 320 } 321 if loginCookie.Value != mockLogin { 322 t.Errorf("Mismatch github login. Got %v, expected %v", loginCookie.Value, mockLogin) 323 } 324 }