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  })