Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v8] Allow CF Authentication based on Tokens - user and client tokens #3455

Open
wants to merge 1 commit into
base: v8
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"))
}
38 changes: 31 additions & 7 deletions command/v7/auth_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,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 with assertion (user) or in combination with client-credentials (non-user)"`
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 CF_NAME auth CLIENT_ID --client-credentials --assertion ACCESS-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 @@ -62,7 +63,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 @@ -75,12 +76,35 @@ func (cmd AuthCommand) Execute(args []string) error {
grantType := constant.GrantTypePassword
if cmd.ClientCredentials {
grantType = constant.GrantTypeClientCredentials
if cmd.Assertion != "" {
if username == "" {
username = cmd.Config.UAAOAuthClient()
}
// 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 {
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 @@ -130,7 +154,7 @@ func (cmd AuthCommand) getUsernamePassword() (string, string, error) {
passwordMissing = false
}

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 @@ -280,6 +280,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
Loading