login_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package login
  2. import (
  3. "context"
  4. "errors"
  5. "io/ioutil"
  6. "net/http"
  7. "net/url"
  8. "os"
  9. "path/filepath"
  10. "reflect"
  11. "testing"
  12. "time"
  13. "github.com/stretchr/testify/mock"
  14. "github.com/stretchr/testify/suite"
  15. "golang.org/x/oauth2"
  16. . "github.com/onsi/gomega"
  17. )
  18. type LoginSuiteTest struct {
  19. suite.Suite
  20. dir string
  21. mockHelper *MockAzureHelper
  22. azureLogin AzureLoginService
  23. }
  24. func (suite *LoginSuiteTest) BeforeTest(suiteName, testName string) {
  25. dir, err := ioutil.TempDir("", "test_store")
  26. Expect(err).To(BeNil())
  27. suite.dir = dir
  28. suite.mockHelper = &MockAzureHelper{}
  29. suite.azureLogin, err = newAzureLoginServiceFromPath(filepath.Join(dir, tokenStoreFilename), suite.mockHelper)
  30. Expect(err).To(BeNil())
  31. }
  32. func (suite *LoginSuiteTest) AfterTest(suiteName, testName string) {
  33. err := os.RemoveAll(suite.dir)
  34. Expect(err).To(BeNil())
  35. }
  36. func (suite *LoginSuiteTest) TestRefreshInValidToken() {
  37. data := refreshTokenData("refreshToken")
  38. suite.mockHelper.On("queryToken", data, "123456").Return(azureToken{
  39. RefreshToken: "newRefreshToken",
  40. AccessToken: "newAccessToken",
  41. ExpiresIn: 3600,
  42. Foci: "1",
  43. }, nil)
  44. //nolint copylocks
  45. azureLogin, err := newAzureLoginServiceFromPath(filepath.Join(suite.dir, tokenStoreFilename), suite.mockHelper)
  46. Expect(err).To(BeNil())
  47. suite.azureLogin = azureLogin
  48. err = suite.azureLogin.tokenStore.writeLoginInfo(TokenInfo{
  49. TenantID: "123456",
  50. Token: oauth2.Token{
  51. AccessToken: "accessToken",
  52. RefreshToken: "refreshToken",
  53. Expiry: time.Now().Add(-1 * time.Hour),
  54. TokenType: "Bearer",
  55. },
  56. })
  57. Expect(err).To(BeNil())
  58. token, _ := suite.azureLogin.GetValidToken()
  59. Expect(token.AccessToken).To(Equal("newAccessToken"))
  60. Expect(token.Expiry).To(BeTemporally(">", time.Now().Add(3500*time.Second)))
  61. storedToken, _ := suite.azureLogin.tokenStore.readToken()
  62. Expect(storedToken.Token.AccessToken).To(Equal("newAccessToken"))
  63. Expect(storedToken.Token.RefreshToken).To(Equal("newRefreshToken"))
  64. Expect(storedToken.Token.Expiry).To(BeTemporally(">", time.Now().Add(3500*time.Second)))
  65. }
  66. func (suite *LoginSuiteTest) TestDoesNotRefreshValidToken() {
  67. expiryDate := time.Now().Add(1 * time.Hour)
  68. err := suite.azureLogin.tokenStore.writeLoginInfo(TokenInfo{
  69. TenantID: "123456",
  70. Token: oauth2.Token{
  71. AccessToken: "accessToken",
  72. RefreshToken: "refreshToken",
  73. Expiry: expiryDate,
  74. TokenType: "Bearer",
  75. },
  76. })
  77. Expect(err).To(BeNil())
  78. token, _ := suite.azureLogin.GetValidToken()
  79. Expect(suite.mockHelper.Calls).To(BeEmpty())
  80. Expect(token.AccessToken).To(Equal("accessToken"))
  81. }
  82. func (suite *LoginSuiteTest) TestInvalidLogin() {
  83. suite.mockHelper.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
  84. redirectURL := args.Get(0).(string)
  85. err := queryKeyValue(redirectURL, "error", "access denied")
  86. Expect(err).To(BeNil())
  87. })
  88. //nolint copylocks
  89. azureLogin, err := newAzureLoginServiceFromPath(filepath.Join(suite.dir, tokenStoreFilename), suite.mockHelper)
  90. Expect(err).To(BeNil())
  91. err = azureLogin.Login(context.TODO())
  92. Expect(err).To(MatchError(errors.New("login failed : [access denied]")))
  93. }
  94. func (suite *LoginSuiteTest) TestValidLogin() {
  95. var redirectURL string
  96. suite.mockHelper.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
  97. redirectURL = args.Get(0).(string)
  98. err := queryKeyValue(redirectURL, "code", "123456879")
  99. Expect(err).To(BeNil())
  100. })
  101. suite.mockHelper.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
  102. //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
  103. return reflect.DeepEqual(data, url.Values{
  104. "grant_type": []string{"authorization_code"},
  105. "client_id": []string{clientID},
  106. "code": []string{"123456879"},
  107. "scope": []string{scopes},
  108. "redirect_uri": []string{redirectURL},
  109. })
  110. }), "organizations").Return(azureToken{
  111. RefreshToken: "firstRefreshToken",
  112. AccessToken: "firstAccessToken",
  113. ExpiresIn: 3600,
  114. Foci: "1",
  115. }, nil)
  116. authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}`
  117. suite.mockHelper.On("queryAuthorizationAPI", authorizationURL, "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
  118. data := refreshTokenData("firstRefreshToken")
  119. suite.mockHelper.On("queryToken", data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
  120. RefreshToken: "newRefreshToken",
  121. AccessToken: "newAccessToken",
  122. ExpiresIn: 3600,
  123. Foci: "1",
  124. }, nil)
  125. //nolint copylocks
  126. azureLogin, err := newAzureLoginServiceFromPath(filepath.Join(suite.dir, tokenStoreFilename), suite.mockHelper)
  127. Expect(err).To(BeNil())
  128. err = azureLogin.Login(context.TODO())
  129. Expect(err).To(BeNil())
  130. loginToken, err := suite.azureLogin.tokenStore.readToken()
  131. Expect(err).To(BeNil())
  132. Expect(loginToken.Token.AccessToken).To(Equal("newAccessToken"))
  133. Expect(loginToken.Token.RefreshToken).To(Equal("newRefreshToken"))
  134. Expect(loginToken.Token.Expiry).To(BeTemporally(">", time.Now().Add(3500*time.Second)))
  135. Expect(loginToken.TenantID).To(Equal("12345a7c-c56d-43e8-9549-dd230ce8a038"))
  136. Expect(loginToken.Token.Type()).To(Equal("Bearer"))
  137. }
  138. func (suite *LoginSuiteTest) TestLoginAuthorizationFailed() {
  139. var redirectURL string
  140. suite.mockHelper.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
  141. redirectURL = args.Get(0).(string)
  142. err := queryKeyValue(redirectURL, "code", "123456879")
  143. Expect(err).To(BeNil())
  144. })
  145. suite.mockHelper.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
  146. //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
  147. return reflect.DeepEqual(data, url.Values{
  148. "grant_type": []string{"authorization_code"},
  149. "client_id": []string{clientID},
  150. "code": []string{"123456879"},
  151. "scope": []string{scopes},
  152. "redirect_uri": []string{redirectURL},
  153. })
  154. }), "organizations").Return(azureToken{
  155. RefreshToken: "firstRefreshToken",
  156. AccessToken: "firstAccessToken",
  157. ExpiresIn: 3600,
  158. Foci: "1",
  159. }, nil)
  160. authBody := `[access denied]`
  161. suite.mockHelper.On("queryAuthorizationAPI", authorizationURL, "Bearer firstAccessToken").Return([]byte(authBody), 400, nil)
  162. azureLogin, err := newAzureLoginServiceFromPath(filepath.Join(suite.dir, tokenStoreFilename), suite.mockHelper)
  163. Expect(err).To(BeNil())
  164. err = azureLogin.Login(context.TODO())
  165. Expect(err).To(MatchError(errors.New("login failed : [access denied]")))
  166. }
  167. func refreshTokenData(refreshToken string) url.Values {
  168. return url.Values{
  169. "grant_type": []string{"refresh_token"},
  170. "client_id": []string{clientID},
  171. "scope": []string{scopes},
  172. "refresh_token": []string{refreshToken},
  173. }
  174. }
  175. func queryKeyValue(redirectURL string, key string, value string) error {
  176. req, err := http.NewRequest("GET", redirectURL, nil)
  177. Expect(err).To(BeNil())
  178. q := req.URL.Query()
  179. q.Add(key, value)
  180. req.URL.RawQuery = q.Encode()
  181. client := &http.Client{}
  182. _, err = client.Do(req)
  183. return err
  184. }
  185. func TestLoginSuite(t *testing.T) {
  186. RegisterTestingT(t)
  187. suite.Run(t, new(LoginSuiteTest))
  188. }
  189. type MockAzureHelper struct {
  190. mock.Mock
  191. }
  192. func (s *MockAzureHelper) queryToken(data url.Values, tenantID string) (token azureToken, err error) {
  193. args := s.Called(data, tenantID)
  194. return args.Get(0).(azureToken), args.Error(1)
  195. }
  196. func (s *MockAzureHelper) queryAuthorizationAPI(authorizationURL string, authorizationHeader string) ([]byte, int, error) {
  197. args := s.Called(authorizationURL, authorizationHeader)
  198. return args.Get(0).([]byte), args.Int(1), args.Error(2)
  199. }
  200. func (s *MockAzureHelper) openAzureLoginPage(redirectURL string) {
  201. s.Called(redirectURL)
  202. }