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