github.com/cloudfoundry/cli@v7.1.0+incompatible/integration/shared/isolated/auth_command_test.go (about) 1 package isolated 2 3 import ( 4 "encoding/json" 5 "io/ioutil" 6 "path/filepath" 7 "time" 8 9 "code.cloudfoundry.org/cli/api/uaa/uaaversion" 10 "code.cloudfoundry.org/cli/integration/helpers" 11 "code.cloudfoundry.org/cli/util/configv3" 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 . "github.com/onsi/gomega/gbytes" 15 . "github.com/onsi/gomega/gexec" 16 ) 17 18 var _ = Describe("auth command", func() { 19 20 BeforeEach(func() { 21 helpers.SkipIfClientCredentialsTestMode() 22 }) 23 24 Context("Help", func() { 25 It("displays the help information", func() { 26 session := helpers.CF("auth", "--help") 27 Eventually(session).Should(Say("NAME:")) 28 Eventually(session).Should(Say("auth - Authenticate non-interactively\n\n")) 29 30 Eventually(session).Should(Say("USAGE:")) 31 Eventually(session).Should(Say("cf auth USERNAME PASSWORD\n")) 32 Eventually(session).Should(Say("cf auth CLIENT_ID CLIENT_SECRET --client-credentials\n\n")) 33 34 Eventually(session).Should(Say("ENVIRONMENT VARIABLES:")) 35 Eventually(session).Should(Say(`CF_USERNAME=user\s+Authenticating user. Overridden if USERNAME argument is provided.`)) 36 Eventually(session).Should(Say(`CF_PASSWORD=password\s+Password associated with user. Overriden if PASSWORD argument is provided.`)) 37 38 Eventually(session).Should(Say("WARNING:")) 39 Eventually(session).Should(Say("Providing your password as a command line option is highly discouraged")) 40 Eventually(session).Should(Say("Your password may be visible to others and may be recorded in your shell history\n")) 41 Eventually(session).Should(Say("Consider using the CF_PASSWORD environment variable instead\n\n")) 42 43 Eventually(session).Should(Say("EXAMPLES:")) 44 Eventually(session).Should(Say("cf auth name@example\\.com \"my password\" \\(use quotes for passwords with a space\\)")) 45 Eventually(session).Should(Say("cf auth name@example\\.com \\\"\\\\\"password\\\\\"\\\" \\(escape quotes if used in password\\)\n\n")) 46 47 Eventually(session).Should(Say("OPTIONS:")) 48 Eventually(session).Should(Say("--client-credentials\\s+Use \\(non-user\\) service account \\(also called client credentials\\)\n")) 49 Eventually(session).Should(Say("--origin\\s+Indicates the identity provider to be used for authentication\n\n")) 50 51 Eventually(session).Should(Say("SEE ALSO:")) 52 Eventually(session).Should(Say("api, login, target")) 53 54 Eventually(session).Should(Exit(0)) 55 }) 56 }) 57 58 When("no positional arguments are provided", func() { 59 Context("and no env variables are provided", func() { 60 It("errors-out with the help information", func() { 61 envWithoutLoginInfo := map[string]string{ 62 "CF_USERNAME": "", 63 "CF_PASSWORD": "", 64 } 65 session := helpers.CFWithEnv(envWithoutLoginInfo, "auth") 66 Eventually(session.Err).Should(Say("Username and password not provided.")) 67 Eventually(session).Should(Say("NAME:")) 68 69 Eventually(session).Should(Exit(1)) 70 }) 71 }) 72 73 When("env variables are provided", func() { 74 It("authenticates the user", func() { 75 username, password := helpers.GetCredentials() 76 env := map[string]string{ 77 "CF_USERNAME": username, 78 "CF_PASSWORD": password, 79 } 80 session := helpers.CFWithEnv(env, "auth") 81 82 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 83 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 84 Eventually(session).Should(Say("OK")) 85 Eventually(session).Should(Say("Use 'cf target' to view or set your target org and space")) 86 87 Eventually(session).Should(Exit(0)) 88 }) 89 }) 90 }) 91 92 When("only a username is provided", func() { 93 It("errors-out with a password required error and the help information", func() { 94 envWithoutLoginInfo := map[string]string{ 95 "CF_USERNAME": "", 96 "CF_PASSWORD": "", 97 } 98 session := helpers.CFWithEnv(envWithoutLoginInfo, "auth", "some-user") 99 Eventually(session.Err).Should(Say("Password not provided.")) 100 Eventually(session).Should(Say("NAME:")) 101 102 Eventually(session).Should(Exit(1)) 103 }) 104 }) 105 106 When("only a password is provided", func() { 107 It("errors-out with a username required error and the help information", func() { 108 env := map[string]string{ 109 "CF_USERNAME": "", 110 "CF_PASSWORD": "some-pass", 111 } 112 session := helpers.CFWithEnv(env, "auth") 113 Eventually(session.Err).Should(Say("Username not provided.")) 114 Eventually(session).Should(Say("NAME:")) 115 116 Eventually(session).Should(Exit(1)) 117 }) 118 }) 119 120 When("extra input is given", func() { 121 It("displays an 'unknown flag' error message", func() { 122 session := helpers.CF("auth", "some-username", "some-password", "-a", "api.bosh-lite.com") 123 124 Eventually(session.Err).Should(Say("Incorrect Usage: unknown flag `a'")) 125 Eventually(session).Should(Say("NAME:")) 126 127 Eventually(session).Should(Exit(1)) 128 }) 129 }) 130 131 When("the API endpoint is not set", func() { 132 BeforeEach(func() { 133 helpers.UnsetAPI() 134 }) 135 136 It("displays an error message", func() { 137 session := helpers.CF("auth", "some-username", "some-password") 138 139 Eventually(session).Should(Say("FAILED")) 140 Eventually(session.Err).Should(Say(`No API endpoint set\. Use 'cf login' or 'cf api' to target an endpoint\.`)) 141 142 Eventually(session).Should(Exit(1)) 143 }) 144 }) 145 146 When("no flags are set (logging in with password grant type)", func() { 147 When("the user provides an invalid username/password combo", func() { 148 BeforeEach(func() { 149 helpers.LoginCF() 150 helpers.TargetOrgAndSpace(ReadOnlyOrg, ReadOnlySpace) 151 }) 152 153 It("clears the cached tokens and target info, then displays an error message", func() { 154 session := helpers.CF("auth", "some-username", "some-password") 155 156 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 157 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 158 Eventually(session).Should(Say("FAILED")) 159 Eventually(session.Err).Should(Say(`Credentials were rejected, please try again\.`)) 160 Eventually(session).Should(Exit(1)) 161 162 // Verify that the user is not logged-in 163 targetSession1 := helpers.CF("target") 164 Eventually(targetSession1.Err).Should(Say(`Not logged in\. Use 'cf login' or 'cf login --sso' to log in\.`)) 165 Eventually(targetSession1).Should(Say("FAILED")) 166 Eventually(targetSession1).Should(Exit(1)) 167 168 // Verify that neither org nor space is targeted 169 helpers.LoginCF() 170 targetSession2 := helpers.CF("target") 171 Eventually(targetSession2).Should(Say("No org or space targeted, use 'cf target -o ORG -s SPACE'")) 172 Eventually(targetSession2).Should(Exit(0)) 173 }) 174 }) 175 176 When("the username and password are valid", func() { 177 It("authenticates the user", func() { 178 username, password := helpers.GetCredentials() 179 session := helpers.CF("auth", username, password) 180 181 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 182 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 183 Eventually(session).Should(Say("OK")) 184 Eventually(session).Should(Say("Use 'cf target' to view or set your target org and space")) 185 186 Eventually(session).Should(Exit(0)) 187 }) 188 }) 189 }) 190 191 When("the 'client-credentials' flag is set", func() { 192 When("the user provides an invalid client id/secret combo", func() { 193 BeforeEach(func() { 194 helpers.LoginCF() 195 helpers.TargetOrgAndSpace(ReadOnlyOrg, ReadOnlySpace) 196 }) 197 198 It("clears the cached tokens and target info, then displays an error message", func() { 199 session := helpers.CF("auth", "some-client-id", "some-client-secret", "--client-credentials") 200 201 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 202 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 203 Eventually(session).Should(Say("FAILED")) 204 Eventually(session.Err).Should(Say(`Credentials were rejected, please try again\.`)) 205 Eventually(session).Should(Exit(1)) 206 207 // Verify that the user is not logged-in 208 targetSession1 := helpers.CF("target") 209 Eventually(targetSession1.Err).Should(Say(`Not logged in\. Use 'cf login' or 'cf login --sso' to log in\.`)) 210 Eventually(targetSession1).Should(Say("FAILED")) 211 Eventually(targetSession1).Should(Exit(1)) 212 213 // Verify that neither org nor space is targeted 214 helpers.LoginCF() 215 targetSession2 := helpers.CF("target") 216 Eventually(targetSession2).Should(Say("No org or space targeted, use 'cf target -o ORG -s SPACE'")) 217 Eventually(targetSession2).Should(Exit(0)) 218 }) 219 }) 220 221 When("the client id and client secret are valid", func() { 222 It("authenticates the user", func() { 223 clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet() 224 session := helpers.CF("auth", clientID, clientSecret, "--client-credentials") 225 226 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 227 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 228 Eventually(session).Should(Say("OK")) 229 Eventually(session).Should(Say("Use 'cf target' to view or set your target org and space")) 230 231 Eventually(session).Should(Exit(0)) 232 }) 233 234 It("writes the client id but does not write the client secret to the config file", func() { 235 clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet() 236 session := helpers.CF("auth", clientID, clientSecret, "--client-credentials") 237 Eventually(session).Should(Exit(0)) 238 239 rawConfig, err := ioutil.ReadFile(filepath.Join(homeDir, ".cf", "config.json")) 240 Expect(err).NotTo(HaveOccurred()) 241 242 Expect(string(rawConfig)).ToNot(ContainSubstring(clientSecret)) 243 244 var configFile configv3.JSONConfig 245 err = json.Unmarshal(rawConfig, &configFile) 246 247 Expect(err).NotTo(HaveOccurred()) 248 Expect(configFile.UAAOAuthClient).To(Equal(clientID)) 249 Expect(configFile.UAAOAuthClientSecret).To(BeEmpty()) 250 Expect(configFile.UAAGrantType).To(Equal("client_credentials")) 251 }) 252 }) 253 }) 254 255 When("a user authenticates with valid client credentials", func() { 256 BeforeEach(func() { 257 clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet() 258 session := helpers.CF("auth", clientID, clientSecret, "--client-credentials") 259 Eventually(session).Should(Exit(0)) 260 }) 261 262 When("a different user authenticates with valid password credentials", func() { 263 It("should fail authentication and display an error informing the user they need to log out", func() { 264 username, password := helpers.GetCredentials() 265 session := helpers.CF("auth", username, password) 266 267 Eventually(session).Should(Say("FAILED")) 268 Eventually(session.Err).Should(Say(`Service account currently logged in\. Use 'cf logout' to log out service account and try again\.`)) 269 Eventually(session).Should(Exit(1)) 270 }) 271 }) 272 273 }) 274 275 When("the origin flag is set", func() { 276 When("the UAA version is too low to use the --origin flag", func() { 277 BeforeEach(func() { 278 helpers.SkipIfUAAVersionAtLeast(uaaversion.MinUAAClientVersion) 279 }) 280 It("prints an error message", func() { 281 session := helpers.CF("auth", "some-username", "some-password", "--client-credentials", "--origin", "garbaje") 282 Eventually(session.Err).Should(Say("Option '--origin' requires UAA API version 4.19.0 or higher. Update your Cloud Foundry instance.")) 283 Eventually(session).Should(Say("FAILED")) 284 Eventually(session).Should(Exit(1)) 285 }) 286 }) 287 288 When("the UAA version is recent enough to support the flag", func() { 289 BeforeEach(func() { 290 helpers.SkipIfUAAVersionLessThan(uaaversion.MinUAAClientVersion) 291 }) 292 When("--client-credentials is also set", func() { 293 It("displays the appropriate error message", func() { 294 session := helpers.CF("auth", "some-username", "some-password", "--client-credentials", "--origin", "garbaje") 295 296 Eventually(session.Err).Should(Say("Incorrect Usage: The following arguments cannot be used together: --client-credentials, --origin")) 297 Eventually(session).Should(Exit(1)) 298 }) 299 }) 300 301 When("a user authenticates with valid user credentials for that origin", func() { 302 var ( 303 username string 304 password string 305 ) 306 307 BeforeEach(func() { 308 username, password = helpers.SkipIfOIDCCredentialsNotSet() 309 }) 310 311 It("authenticates the user", func() { 312 session := helpers.CF("auth", username, password, "--origin", "cli-oidc-provider") 313 314 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 315 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 316 Eventually(session).Should(Say("OK")) 317 Eventually(session).Should(Say("Use 'cf target' to view or set your target org and space")) 318 Eventually(session).Should(Exit(0)) 319 }) 320 }) 321 322 When("the user provides the default origin and valid credentials", func() { 323 It("authenticates the user", func() { 324 username, password := helpers.GetCredentials() 325 session := helpers.CF("auth", username, password, "--origin", "uaa") 326 327 Eventually(session).Should(Say("API endpoint: %s", helpers.GetAPI())) 328 Eventually(session).Should(Say(`Authenticating\.\.\.`)) 329 Eventually(session).Should(Say("OK")) 330 Eventually(session).Should(Say("Use 'cf target' to view or set your target org and space")) 331 Eventually(session).Should(Exit(0)) 332 }) 333 }) 334 335 When("when the user provides an invalid origin", func() { 336 It("returns an error", func() { 337 session := helpers.CF("auth", "some-user", "some-password", "--origin", "EA") 338 Eventually(session.Err).Should(Say("The origin provided is invalid.")) 339 Eventually(session).Should(Say("FAILED")) 340 Eventually(session).Should(Exit(1)) 341 }) 342 }) 343 }) 344 }) 345 346 Describe("Authenticating as a user, through a custom client", func() { 347 var accessTokenExpiration time.Duration 348 var session *Session 349 BeforeEach(func() { 350 customClientID, customClientSecret := helpers.SkipIfCustomClientCredentialsNotSet() 351 352 helpers.LoginCF() 353 username, password := helpers.CreateUser() 354 355 helpers.SetConfig(func(config *configv3.Config) { 356 config.ConfigFile.UAAOAuthClient = customClientID 357 config.ConfigFile.UAAOAuthClientSecret = customClientSecret 358 config.ConfigFile.UAAGrantType = "" 359 }) 360 361 session = helpers.CF("auth", username, password) 362 Eventually(session).Should(Exit(0)) 363 accessTokenExpiration = 120 // this was configured in the pipeline 364 }) 365 366 It("access token validity matches custom client configuration", func() { 367 config := helpers.GetConfig() 368 369 jwt := helpers.ParseTokenString(config.ConfigFile.AccessToken) 370 expires, expIsSet := jwt.Claims().Expiration() 371 Expect(expIsSet).To(BeTrue()) 372 373 iat, iatIsSet := jwt.Claims().IssuedAt() 374 375 Expect(iatIsSet).To(BeTrue()) 376 Expect(expires.Sub(iat)).To(Equal(accessTokenExpiration * time.Second)) 377 }) 378 379 It("shows a deprecation warning", func() { 380 Eventually(session.Err).Should(Say("Deprecation warning: Manually writing your client credentials to the config.json is deprecated and will be removed in the future. For similar functionality, please use the `cf auth --client-credentials` command instead.")) 381 }) 382 383 When("the token has expired", func() { 384 BeforeEach(func() { 385 helpers.SetConfig(func(config *configv3.Config) { 386 config.ConfigFile.AccessToken = helpers.ExpiredAccessToken() 387 }) 388 }) 389 390 It("re-authenticates using the custom client", func() { 391 session := helpers.CF("orgs") 392 Eventually(session).Should(Exit(0)) 393 }) 394 }) 395 }) 396 })