Skip to content

Commit

Permalink
Allow CF Authentication based on Tokens - user and client tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
strehle committed Feb 11, 2025
1 parent 8ca11b6 commit b21187b
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 10 deletions.
4 changes: 2 additions & 2 deletions actor/v7action/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewDefaultAuthActor(config Config, uaaClient UAAClient) AuthActor {
}

func (actor defaultAuthActor) Authenticate(credentials map[string]string, origin string, grantType constant.GrantType) error {
if grantType == constant.GrantTypePassword && actor.config.UAAGrantType() == string(constant.GrantTypeClientCredentials) {
if (grantType == constant.GrantTypePassword || grantType == constant.GrantTypeJwtBearer) && actor.config.UAAGrantType() == string(constant.GrantTypeClientCredentials) {
return actionerror.PasswordGrantTypeLogoutRequiredError{}
}

Expand All @@ -45,7 +45,7 @@ func (actor defaultAuthActor) Authenticate(credentials map[string]string, origin
actor.config.SetUAAGrantType(string(grantType))
}

if grantType == constant.GrantTypeClientCredentials {
if (grantType == constant.GrantTypeClientCredentials || grantType == constant.GrantTypeJwtBearer) && credentials["client_id"] != "" {
actor.config.SetUAAClientCredentials(credentials["client_id"], "")
}

Expand Down
11 changes: 11 additions & 0 deletions api/uaa/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ func (client Client) Authenticate(creds map[string]string, origin string, grantT

if grantType == constant.GrantTypePassword {
request.SetBasicAuth(client.config.UAAOAuthClient(), client.config.UAAOAuthClientSecret())
} else if grantType == constant.GrantTypeJwtBearer {
// overwrite client authentication in case of provided parameters in cf auth clientid clientsecret or use defaults as done in password grant
clientId := client.config.UAAOAuthClient()
clientSecret := client.config.UAAOAuthClientSecret()
if creds["client_id"] != "" {
clientId = creds["client_id"]
}
if creds["client_secret"] != "" {
clientSecret = creds["client_secret"]
}
request.SetBasicAuth(clientId, clientSecret)
}

responseBody := AuthResponse{}
Expand Down
31 changes: 31 additions & 0 deletions api/uaa/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,37 @@ var _ = Describe("Auth", func() {
Expect(refreshToken).To(BeEmpty())
})
})

When("the grant type is jwt bearer assertion", func() {
BeforeEach(func() {
response := `{
"access_token":"some-access-token"
}`

credentials = map[string]string{
"client_id": "some-client-id",
"client_secret": "some-client-secret",
}
origin = ""
grantType = constant.GrantTypeJwtBearer
server.AppendHandlers(
CombineHandlers(
verifyRequestHost(TestAuthorizationResource),
VerifyRequest(http.MethodPost, "/oauth/token"),
VerifyHeaderKV("Content-Type", "application/x-www-form-urlencoded"),
VerifyHeaderKV("Authorization", "Basic c29tZS1jbGllbnQtaWQ6c29tZS1jbGllbnQtc2VjcmV0"),
VerifyBody([]byte(fmt.Sprintf("client_id=%s&client_secret=%s&grant_type=%s", credentials["client_id"], credentials["client_secret"], url.QueryEscape(string(grantType))))),
RespondWith(http.StatusOK, response),
))
})

It("authenticates with the assertion provided", func() {
Expect(executeErr).NotTo(HaveOccurred())

Expect(accessToken).To(Equal("some-access-token"))
Expect(refreshToken).To(BeEmpty())
})
})
})

When("an error occurs", func() {
Expand Down
2 changes: 2 additions & 0 deletions api/uaa/constant/grant_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ const (
// GrantTypePassword is used for user's username/password authentication.
GrantTypePassword GrantType = "password"
GrantTypeRefreshToken GrantType = "refresh_token"
// GrantTypeJwtBearer is used for token based user authentication
GrantTypeJwtBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
)
2 changes: 1 addition & 1 deletion api/uaa/refresh_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (client *Client) RefreshAccessToken(refreshToken string) (RefreshedTokens,
switch client.config.UAAGrantType() {
case string(constant.GrantTypeClientCredentials):
values = client.clientCredentialRefreshBody()
case "", string(constant.GrantTypePassword): // CLI used to write empty string for grant type in the case of password; preserve compatibility with old config.json files
case "", string(constant.GrantTypePassword), string(constant.GrantTypeJwtBearer): // CLI used to write empty string for grant type in the case of password; preserve compatibility with old config.json files
values = client.refreshTokenBody(refreshToken)
}

Expand Down
1 change: 1 addition & 0 deletions api/uaa/wrapper/uaa_authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,6 @@ func skipAuthenticationHeader(request *http.Request, body []byte) bool {
request.Method == http.MethodPost &&
(strings.Contains(stringBody, "grant_type=refresh_token") ||
strings.Contains(stringBody, "grant_type=password") ||
strings.Contains(stringBody, "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer") ||
strings.Contains(stringBody, "grant_type=client_credentials"))
}
35 changes: 28 additions & 7 deletions command/v7/auth_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ type AuthCommand struct {
RequiredArgs flag.Authentication `positional-args:"yes"`
ClientCredentials bool `long:"client-credentials" description:"Use (non-user) service account (also called client credentials)"`
Origin string `long:"origin" description:"Indicates the identity provider to be used for authentication"`
usage interface{} `usage:"CF_NAME auth USERNAME PASSWORD\n CF_NAME auth USERNAME PASSWORD --origin ORIGIN\n CF_NAME auth CLIENT_ID CLIENT_SECRET --client-credentials\n\nENVIRONMENT VARIABLES:\n CF_USERNAME=user Authenticating user. Overridden if USERNAME argument is provided.\n CF_PASSWORD=password Password associated with user. Overridden if PASSWORD argument is provided.\n\nWARNING:\n Providing your password as a command line option is highly discouraged\n Your password may be visible to others and may be recorded in your shell history\n Consider using the CF_PASSWORD environment variable instead\n\nEXAMPLES:\n CF_NAME auth [email protected] \"my password\" (use quotes for passwords with a space)\n CF_NAME auth [email protected] \"\\\"password\\\"\" (escape quotes if used in password)"`
Assertion string `long:"assertion" description:"Token based authentication based on assertion"`
usage interface{} `usage:"CF_NAME auth USERNAME PASSWORD\n CF_NAME auth USERNAME PASSWORD --origin ORIGIN\n CF_NAME auth CLIENT_ID CLIENT_SECRET --client-credentials\n CF_NAME auth CLIENT_ID CLIENT_SECRET --assertion ID-TOKEN\n\nENVIRONMENT VARIABLES:\n CF_USERNAME=user Authenticating user. Overridden if USERNAME argument is provided.\n CF_PASSWORD=password Password associated with user. Overridden if PASSWORD argument is provided.\n\nWARNING:\n Providing your password as a command line option is highly discouraged\n Your password may be visible to others and may be recorded in your shell history\n Consider using the CF_PASSWORD environment variable instead\n\nEXAMPLES:\n CF_NAME auth [email protected] \"my password\" (use quotes for passwords with a space)\n CF_NAME auth [email protected] \"\\\"password\\\"\" (escape quotes if used in password)"`
relatedCommands interface{} `related_commands:"api, login, target"`
}

Expand Down Expand Up @@ -63,7 +64,7 @@ func (cmd AuthCommand) Execute(args []string) error {
if !cmd.ClientCredentials {
if cmd.Config.UAAGrantType() == string(constant.GrantTypeClientCredentials) {
return translatableerror.PasswordGrantTypeLogoutRequiredError{}
} else if cmd.Config.UAAOAuthClient() != "cf" || cmd.Config.UAAOAuthClientSecret() != "" {
} else if (cmd.Assertion == "" && cmd.Config.UAAOAuthClient() != "cf") || cmd.Config.UAAOAuthClientSecret() != "" {
return translatableerror.ManualClientCredentialsError{}
}
}
Expand All @@ -76,8 +77,14 @@ func (cmd AuthCommand) Execute(args []string) error {
grantType := constant.GrantTypePassword
if cmd.ClientCredentials {
grantType = constant.GrantTypeClientCredentials
if cmd.Assertion != "" {
// use assertion as client_assertion - replacing client_secret - but stay with client credentials grant type
credentials["client_assertion"] = cmd.Assertion
credentials["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
} else {
credentials["client_secret"] = password
}
credentials["client_id"] = username
credentials["client_secret"] = password
} else if cmd.Config.IsCFOnK8s() {
prompts, err := cmd.Actor.GetLoginPrompts()
if err != nil {
Expand All @@ -102,9 +109,23 @@ func (cmd AuthCommand) Execute(args []string) error {
"k8s-auth-info": username,
}
} else {
credentials = map[string]string{
"username": username,
"password": password,
if cmd.Assertion != "" {
// use assertion as user authentication using JWT bearer grant type
grantType = constant.GrantTypeJwtBearer
credentials = map[string]string{
"assertion": cmd.Assertion,
}
if username != "" {
credentials["client_id"] = username
}
if password != "" {
credentials["client_secret"] = password
}
} else {
credentials = map[string]string{
"username": username,
"password": password,
}
}
}

Expand Down Expand Up @@ -147,7 +168,7 @@ func (cmd AuthCommand) getUsernamePassword() (string, string, error) {
}
}

if userMissing || passwordMissing {
if cmd.Assertion == "" && (userMissing || passwordMissing) {
return "", "", translatableerror.MissingCredentialsError{
MissingUsername: userMissing,
MissingPassword: passwordMissing,
Expand Down
59 changes: 59 additions & 0 deletions command/v7/auth_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,65 @@ var _ = Describe("auth Command", func() {
})
})

When("--assertion is set", func() {
BeforeEach(func() {
cmd.ClientCredentials = false
cmd.Assertion = "jwt-token"
cmd.RequiredArgs.Username = testID
cmd.RequiredArgs.Password = testSecret
})

It("outputs API target information and clears the targeted org and space", func() {
Expect(err).ToNot(HaveOccurred())

Expect(testUI.Out).To(Say("API endpoint: %s", fakeConfig.Target()))
Expect(testUI.Out).To(Say(`Authenticating\.\.\.`))
Expect(testUI.Out).To(Say("OK"))
Expect(testUI.Out).To(Say("Use '%s target' to view or set your target org and space", binaryName))

Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0)
ID := credentials["client_id"]
secret := credentials["client_secret"]
Expect(ID).To(Equal(testID))
Expect(secret).To(Equal(testSecret))
Expect(origin).To(BeEmpty())
Expect(grantType).To(Equal(constant.GrantTypeJwtBearer))
})
})

When("--assertion and --client-credentials is set", func() {
BeforeEach(func() {
cmd.ClientCredentials = true
cmd.Assertion = "client-jwt-token"
cmd.RequiredArgs.Username = testID
cmd.RequiredArgs.Password = testSecret
})

It("outputs API target information and clears the targeted org and space", func() {
Expect(err).ToNot(HaveOccurred())

Expect(testUI.Out).To(Say("API endpoint: %s", fakeConfig.Target()))
Expect(testUI.Out).To(Say(`Authenticating\.\.\.`))
Expect(testUI.Out).To(Say("OK"))
Expect(testUI.Out).To(Say("Use '%s target' to view or set your target org and space", binaryName))

Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0)
ID := credentials["client_id"]
secret := credentials["client_secret"]
clientAssertion := credentials["client_assertion"]
clientAssertionType := credentials["client_assertion_type"]
Expect(ID).To(Equal(testID))
Expect(secret).To(BeEmpty())
Expect(origin).To(BeEmpty())
Expect(secret).To(Equal(""))
Expect(clientAssertion).To(Equal("client-jwt-token"))
Expect(clientAssertionType).To(Equal("urn:ietf:params:oauth:client-assertion-type:jwt-bearer"))
Expect(grantType).To(Equal(constant.GrantTypeClientCredentials))
})
})

When("the username and password are provided in env variables", func() {
var (
envUsername string
Expand Down

0 comments on commit b21187b

Please sign in to comment.