github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/cf/api/authentication/authentication_test.go (about)

     1  package authentication_test
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  
     9  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    10  	"code.cloudfoundry.org/cli/cf/net"
    11  	"code.cloudfoundry.org/cli/cf/terminal/terminalfakes"
    12  	testconfig "code.cloudfoundry.org/cli/util/testhelpers/configuration"
    13  	testnet "code.cloudfoundry.org/cli/util/testhelpers/net"
    14  
    15  	. "code.cloudfoundry.org/cli/cf/api/authentication"
    16  	"code.cloudfoundry.org/cli/cf/trace/tracefakes"
    17  	. "code.cloudfoundry.org/cli/util/testhelpers/matchers"
    18  	. "github.com/onsi/ginkgo"
    19  	. "github.com/onsi/gomega"
    20  	"github.com/onsi/gomega/ghttp"
    21  )
    22  
    23  var _ = Describe("AuthenticationRepository", func() {
    24  	Describe("legacy tests", func() {
    25  		var (
    26  			gateway     net.Gateway
    27  			testServer  *httptest.Server
    28  			handler     *testnet.TestHandler
    29  			config      coreconfig.ReadWriter
    30  			auth        Repository
    31  			dumper      net.RequestDumper
    32  			fakePrinter *tracefakes.FakePrinter
    33  		)
    34  
    35  		BeforeEach(func() {
    36  			config = testconfig.NewRepository()
    37  			fakePrinter = new(tracefakes.FakePrinter)
    38  			gateway = net.NewUAAGateway(config, new(terminalfakes.FakeUI), fakePrinter, "")
    39  			dumper = net.NewRequestDumper(fakePrinter)
    40  			auth = NewUAARepository(gateway, config, dumper)
    41  		})
    42  
    43  		AfterEach(func() {
    44  			testServer.Close()
    45  		})
    46  
    47  		var setupTestServer = func(request testnet.TestRequest) {
    48  			testServer, handler = testnet.NewServer([]testnet.TestRequest{request})
    49  			config.SetAuthenticationEndpoint(testServer.URL)
    50  			config.SetUAAOAuthClient("cf")
    51  		}
    52  
    53  		Describe("authenticating", func() {
    54  			var err error
    55  
    56  			JustBeforeEach(func() {
    57  				err = auth.Authenticate(map[string]string{
    58  					"username": "foo@example.com",
    59  					"password": "bar",
    60  				})
    61  			})
    62  
    63  			Describe("when login succeeds", func() {
    64  				BeforeEach(func() {
    65  					setupTestServer(successfulLoginRequest)
    66  				})
    67  
    68  				It("stores the access and refresh tokens in the config", func() {
    69  					Expect(handler).To(HaveAllRequestsCalled())
    70  					Expect(err).NotTo(HaveOccurred())
    71  					Expect(config.AuthenticationEndpoint()).To(Equal(testServer.URL))
    72  					Expect(config.AccessToken()).To(Equal("BEARER my_access_token"))
    73  					Expect(config.RefreshToken()).To(Equal("my_refresh_token"))
    74  				})
    75  			})
    76  
    77  			Describe("when login fails", func() {
    78  				BeforeEach(func() {
    79  					setupTestServer(unsuccessfulLoginRequest)
    80  				})
    81  
    82  				It("returns an error", func() {
    83  					Expect(handler).To(HaveAllRequestsCalled())
    84  					Expect(err).NotTo(BeNil())
    85  					Expect(err.Error()).To(Equal("Credentials were rejected, please try again."))
    86  					Expect(config.AccessToken()).To(BeEmpty())
    87  					Expect(config.RefreshToken()).To(BeEmpty())
    88  				})
    89  			})
    90  
    91  			Context("when the authentication server returns status code 500", func() {
    92  				BeforeEach(func() {
    93  					setupTestServer(errorLoginRequest)
    94  				})
    95  
    96  				It("returns a failure response", func() {
    97  					Expect(handler).To(HaveAllRequestsCalled())
    98  					Expect(err).To(HaveOccurred())
    99  					Expect(err.Error()).To(Equal("The targeted API endpoint could not be reached."))
   100  					Expect(config.AccessToken()).To(BeEmpty())
   101  				})
   102  			})
   103  
   104  			Context("when the authentication server returns status code 502", func() {
   105  				var request testnet.TestRequest
   106  
   107  				BeforeEach(func() {
   108  					request = testnet.TestRequest{
   109  						Method: "POST",
   110  						Path:   "/oauth/token",
   111  						Response: testnet.TestResponse{
   112  							Status: http.StatusBadGateway,
   113  						},
   114  					}
   115  					setupTestServer(request)
   116  				})
   117  
   118  				It("returns a failure response", func() {
   119  					Expect(handler).To(HaveAllRequestsCalled())
   120  					Expect(err).To(HaveOccurred())
   121  					Expect(err.Error()).To(Equal("The targeted API endpoint could not be reached."))
   122  					Expect(config.AccessToken()).To(BeEmpty())
   123  				})
   124  			})
   125  
   126  			Describe("when the UAA server has an error but still returns a 200", func() {
   127  				BeforeEach(func() {
   128  					setupTestServer(errorMaskedAsSuccessLoginRequest)
   129  				})
   130  
   131  				It("returns an error", func() {
   132  					Expect(handler).To(HaveAllRequestsCalled())
   133  					Expect(err).To(HaveOccurred())
   134  					Expect(err.Error()).To(ContainSubstring("I/O error: uaa.10.244.0.22.xip.io; nested exception is java.net.UnknownHostException: uaa.10.244.0.22.xip.io"))
   135  					Expect(config.AccessToken()).To(BeEmpty())
   136  				})
   137  			})
   138  		})
   139  
   140  		Describe("getting login info", func() {
   141  			var (
   142  				apiErr  error
   143  				prompts map[string]coreconfig.AuthPrompt
   144  			)
   145  
   146  			JustBeforeEach(func() {
   147  				prompts, apiErr = auth.GetLoginPromptsAndSaveUAAServerURL()
   148  			})
   149  
   150  			Describe("when the login info API succeeds", func() {
   151  				BeforeEach(func() {
   152  					setupTestServer(loginServerLoginRequest)
   153  				})
   154  
   155  				It("does not return an error", func() {
   156  					Expect(apiErr).NotTo(HaveOccurred())
   157  				})
   158  
   159  				It("gets the login prompts", func() {
   160  					Expect(prompts).To(Equal(map[string]coreconfig.AuthPrompt{
   161  						"username": {
   162  							DisplayName: "Email",
   163  							Type:        coreconfig.AuthPromptTypeText,
   164  						},
   165  						"pin": {
   166  							DisplayName: "PIN Number",
   167  							Type:        coreconfig.AuthPromptTypePassword,
   168  						},
   169  					}))
   170  				})
   171  
   172  				It("saves the UAA server to the config", func() {
   173  					Expect(config.UaaEndpoint()).To(Equal("https://uaa.run.pivotal.io"))
   174  				})
   175  			})
   176  
   177  			Describe("when the login info API fails", func() {
   178  				BeforeEach(func() {
   179  					setupTestServer(loginServerLoginFailureRequest)
   180  				})
   181  
   182  				It("returns a failure response when the login info API fails", func() {
   183  					Expect(handler).To(HaveAllRequestsCalled())
   184  					Expect(apiErr).To(HaveOccurred())
   185  					Expect(prompts).To(BeEmpty())
   186  				})
   187  			})
   188  
   189  			Context("when the response does not contain links", func() {
   190  				BeforeEach(func() {
   191  					setupTestServer(uaaServerLoginRequest)
   192  				})
   193  
   194  				It("presumes that the authorization server is the UAA", func() {
   195  					Expect(config.UaaEndpoint()).To(Equal(config.AuthenticationEndpoint()))
   196  				})
   197  			})
   198  		})
   199  
   200  		Describe("refreshing the auth token", func() {
   201  			var apiErr error
   202  
   203  			JustBeforeEach(func() {
   204  				_, apiErr = auth.RefreshAuthToken()
   205  			})
   206  
   207  			Context("when the refresh token has expired", func() {
   208  				BeforeEach(func() {
   209  					setupTestServer(refreshTokenExpiredRequestError)
   210  				})
   211  				It("the returns the reauthentication error message", func() {
   212  					Expect(apiErr.Error()).To(Equal("Authentication has expired.  Please log back in to re-authenticate.\n\nTIP: Use `cf login -a <endpoint> -u <user> -o <org> -s <space>` to log back in and re-authenticate."))
   213  				})
   214  			})
   215  			Context("when there is a UAA error", func() {
   216  				BeforeEach(func() {
   217  					setupTestServer(errorLoginRequest)
   218  				})
   219  
   220  				It("returns the API error", func() {
   221  					Expect(apiErr).NotTo(BeNil())
   222  				})
   223  			})
   224  		})
   225  	})
   226  
   227  	Describe("Authorize", func() {
   228  		var (
   229  			uaaServer   *ghttp.Server
   230  			gateway     net.Gateway
   231  			config      coreconfig.ReadWriter
   232  			authRepo    Repository
   233  			dumper      net.RequestDumper
   234  			fakePrinter *tracefakes.FakePrinter
   235  		)
   236  
   237  		BeforeEach(func() {
   238  			uaaServer = ghttp.NewServer()
   239  			config = testconfig.NewRepository()
   240  			config.SetUaaEndpoint(uaaServer.URL())
   241  			config.SetSSHOAuthClient("ssh-oauth-client")
   242  
   243  			fakePrinter = new(tracefakes.FakePrinter)
   244  			gateway = net.NewUAAGateway(config, new(terminalfakes.FakeUI), fakePrinter, "")
   245  			dumper = net.NewRequestDumper(fakePrinter)
   246  			authRepo = NewUAARepository(gateway, config, dumper)
   247  
   248  			uaaServer.AppendHandlers(
   249  				ghttp.CombineHandlers(
   250  					ghttp.VerifyHeader(http.Header{"authorization": []string{"auth-token"}}),
   251  					ghttp.VerifyRequest("GET", "/oauth/authorize",
   252  						"response_type=code&grant_type=authorization_code&client_id=ssh-oauth-client",
   253  					),
   254  					ghttp.RespondWith(http.StatusFound, ``, http.Header{
   255  						"Location": []string{"https://www.cloudfoundry.example.com?code=F45jH"},
   256  					}),
   257  				),
   258  			)
   259  		})
   260  
   261  		AfterEach(func() {
   262  			uaaServer.Close()
   263  		})
   264  
   265  		It("requests the one time code", func() {
   266  			_, err := authRepo.Authorize("auth-token")
   267  			Expect(err).NotTo(HaveOccurred())
   268  			Expect(uaaServer.ReceivedRequests()).To(HaveLen(1))
   269  		})
   270  
   271  		It("returns the one time code", func() {
   272  			code, err := authRepo.Authorize("auth-token")
   273  			Expect(err).NotTo(HaveOccurred())
   274  			Expect(code).To(Equal("F45jH"))
   275  		})
   276  
   277  		Context("when the authentication endpoint is malformed", func() {
   278  			BeforeEach(func() {
   279  				config.SetUaaEndpoint(":not-well-formed")
   280  			})
   281  
   282  			It("returns an error", func() {
   283  				_, err := authRepo.Authorize("auth-token")
   284  				Expect(err).To(HaveOccurred())
   285  			})
   286  		})
   287  
   288  		Context("when the authorization server does not return a redirect", func() {
   289  			BeforeEach(func() {
   290  				uaaServer.SetHandler(0, ghttp.RespondWith(http.StatusOK, ``))
   291  			})
   292  
   293  			It("returns an error", func() {
   294  				_, err := authRepo.Authorize("auth-token")
   295  				Expect(err).To(HaveOccurred())
   296  				Expect(err.Error()).To(Equal("Authorization server did not redirect with one time code"))
   297  			})
   298  		})
   299  
   300  		Context("when the authorization server does not return a redirect", func() {
   301  			BeforeEach(func() {
   302  				config.SetUaaEndpoint("https://127.0.0.1:1")
   303  			})
   304  
   305  			It("returns an error", func() {
   306  				_, err := authRepo.Authorize("auth-token")
   307  				Expect(err).To(HaveOccurred())
   308  				Expect(err.Error()).To(ContainSubstring("Error requesting one time code from server"))
   309  			})
   310  		})
   311  
   312  		Context("when the authorization server returns multiple codes", func() {
   313  			BeforeEach(func() {
   314  				uaaServer.SetHandler(0, ghttp.RespondWith(http.StatusFound, ``, http.Header{
   315  					"Location": []string{"https://www.cloudfoundry.example.com?code=F45jH&code=LLLLL"},
   316  				}))
   317  			})
   318  
   319  			It("returns an error", func() {
   320  				_, err := authRepo.Authorize("auth-token")
   321  				Expect(err).To(HaveOccurred())
   322  				Expect(err.Error()).To(Equal("Unable to acquire one time code from authorization response"))
   323  			})
   324  		})
   325  	})
   326  })
   327  
   328  var authHeaders = http.Header{
   329  	"accept":        {"application/json"},
   330  	"content-type":  {"application/x-www-form-urlencoded"},
   331  	"authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("cf:"))},
   332  }
   333  
   334  var successfulLoginRequest = testnet.TestRequest{
   335  	Method:  "POST",
   336  	Path:    "/oauth/token",
   337  	Header:  authHeaders,
   338  	Matcher: successfulLoginMatcher,
   339  	Response: testnet.TestResponse{
   340  		Status: http.StatusOK,
   341  		Body: `
   342  {
   343    "access_token": "my_access_token",
   344    "token_type": "BEARER",
   345    "refresh_token": "my_refresh_token",
   346    "scope": "openid",
   347    "expires_in": 98765
   348  } `},
   349  }
   350  
   351  var successfulLoginMatcher = func(request *http.Request) {
   352  	err := request.ParseForm()
   353  	if err != nil {
   354  		Fail(fmt.Sprintf("Failed to parse form: %s", err))
   355  		return
   356  	}
   357  
   358  	Expect(request.Form.Get("username")).To(Equal("foo@example.com"))
   359  	Expect(request.Form.Get("password")).To(Equal("bar"))
   360  	Expect(request.Form.Get("grant_type")).To(Equal("password"))
   361  	Expect(request.Form.Get("scope")).To(Equal(""))
   362  }
   363  
   364  var unsuccessfulLoginRequest = testnet.TestRequest{
   365  	Method: "POST",
   366  	Path:   "/oauth/token",
   367  	Response: testnet.TestResponse{
   368  		Status: http.StatusUnauthorized,
   369  	},
   370  }
   371  var refreshTokenExpiredRequestError = testnet.TestRequest{
   372  	Method: "POST",
   373  	Path:   "/oauth/token",
   374  	Response: testnet.TestResponse{
   375  		Status: http.StatusUnauthorized,
   376  		Body: `
   377  {
   378  	"error": "invalid_token",
   379  	"error_description": "Invalid auth token: Invalid refresh token (expired): eyJhbGckjsdfdf"
   380  }
   381  `},
   382  }
   383  
   384  var errorLoginRequest = testnet.TestRequest{
   385  	Method: "POST",
   386  	Path:   "/oauth/token",
   387  	Response: testnet.TestResponse{
   388  		Status: http.StatusInternalServerError,
   389  	},
   390  }
   391  
   392  var errorMaskedAsSuccessLoginRequest = testnet.TestRequest{
   393  	Method: "POST",
   394  	Path:   "/oauth/token",
   395  	Response: testnet.TestResponse{
   396  		Status: http.StatusOK,
   397  		Body: `
   398  {
   399  	"error": {
   400  		"error": "rest_client_error",
   401  		"error_description": "I/O error: uaa.10.244.0.22.xip.io; nested exception is java.net.UnknownHostException: uaa.10.244.0.22.xip.io"
   402  	}
   403  }
   404  `},
   405  }
   406  
   407  var loginServerLoginRequest = testnet.TestRequest{
   408  	Method: "GET",
   409  	Path:   "/login",
   410  	Response: testnet.TestResponse{
   411  		Status: http.StatusOK,
   412  		Body: `
   413  {
   414  	"timestamp":"2013-12-18T11:26:53-0700",
   415  	"app":{
   416  		"artifact":"cloudfoundry-identity-uaa",
   417  		"description":"User Account and Authentication Service",
   418  		"name":"UAA",
   419  		"version":"1.4.7"
   420  	},
   421  	"commit_id":"2701cc8",
   422  	"links":{
   423  	    "register":"https://console.run.pivotal.io/register",
   424  	    "passwd":"https://console.run.pivotal.io/password_resets/new",
   425  	    "home":"https://console.run.pivotal.io",
   426  	    "support":"https://support.cloudfoundry.com/home",
   427  	    "login":"https://login.run.pivotal.io",
   428  	    "uaa":"https://uaa.run.pivotal.io"
   429  	 },
   430  	"prompts":{
   431  		"username": ["text","Email"],
   432  		"pin": ["password", "PIN Number"]
   433  	}
   434  }`,
   435  	},
   436  }
   437  
   438  var loginServerLoginFailureRequest = testnet.TestRequest{
   439  	Method: "GET",
   440  	Path:   "/login",
   441  	Response: testnet.TestResponse{
   442  		Status: http.StatusInternalServerError,
   443  	},
   444  }
   445  
   446  var uaaServerLoginRequest = testnet.TestRequest{
   447  	Method: "GET",
   448  	Path:   "/login",
   449  	Response: testnet.TestResponse{
   450  		Status: http.StatusOK,
   451  		Body: `
   452  {
   453  	"timestamp":"2013-12-18T11:26:53-0700",
   454  	"app":{
   455  		"artifact":"cloudfoundry-identity-uaa",
   456  		"description":"User Account and Authentication Service",
   457  		"name":"UAA",
   458  		"version":"1.4.7"
   459  	},
   460  	"commit_id":"2701cc8",
   461  	"prompts":{
   462  		"username": ["text","Email"],
   463  		"pin": ["password", "PIN Number"]
   464  	}
   465  }`,
   466  	},
   467  }