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  }