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

Support Platform Access Token #2193

Merged
merged 9 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
153 changes: 146 additions & 7 deletions access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/jfrog/jfrog-cli-core/v2/common/commands"
coreenvsetup "github.com/jfrog/jfrog-cli-core/v2/general/envsetup"
coreEnvSetup "github.com/jfrog/jfrog-cli-core/v2/general/envsetup"
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests"
coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests"
"github.com/jfrog/jfrog-cli/utils/tests"
"github.com/jfrog/jfrog-client-go/auth"
clientUtils "github.com/jfrog/jfrog-client-go/utils"
clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests"
"github.com/stretchr/testify/assert"
"testing"
)

var (
accessDetails *config.ServerDetails
accessCli *tests.JfrogCli
)

func initAccessTest(t *testing.T) {
if !*tests.TestAccess {
t.Skip("Skipping Access test. To run Access test add the '-test.access=true' option.")
Expand All @@ -23,13 +30,13 @@ func initAccessTest(t *testing.T) {

func TestSetupInvitedUser(t *testing.T) {
initAccessTest(t)
tempDirPath, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t)
tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t)
defer createTempDirCallback()
setEnvCallBack := clientTestUtils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, tempDirPath)
defer setEnvCallBack()
serverDetails := &config.ServerDetails{Url: *tests.JfrogUrl, AccessToken: *tests.JfrogAccessToken}
encodedCred := encodeConnectionDetails(serverDetails, t)
setupCmd := coreenvsetup.NewEnvSetupCommand().SetEncodedConnectionDetails(encodedCred)
setupServerDetails := &config.ServerDetails{Url: *tests.JfrogUrl, AccessToken: *tests.JfrogAccessToken}
encodedCred := encodeConnectionDetails(setupServerDetails, t)
setupCmd := coreEnvSetup.NewEnvSetupCommand().SetEncodedConnectionDetails(encodedCred)
suffix := setupCmd.SetupAndConfigServer()
assert.Empty(t, suffix)
configs, err := config.GetAllServersConfigs()
Expand All @@ -54,7 +61,7 @@ func TestRefreshableAccessTokens(t *testing.T) {
initAccessTest(t)

server := &config.ServerDetails{Url: *tests.JfrogUrl, AccessToken: *tests.JfrogAccessToken}
err := coreenvsetup.GenerateNewLongTermRefreshableAccessToken(server)
err := coreEnvSetup.GenerateNewLongTermRefreshableAccessToken(server)
assert.NoError(t, err)
assert.NotEmpty(t, server.RefreshToken)
configCmd := commands.NewConfigCommand(commands.AddOrEdit, tests.ServerId).SetDetails(server).SetInteractive(false)
Expand Down Expand Up @@ -115,3 +122,135 @@ func getAccessTokensFromConfig(t *testing.T, serverId string) (accessToken, refr
}
return details.AccessToken, details.RefreshToken, nil
}

const (
userScope = "applied-permissions/user"
defaultExpiry = 31536000
)

func TestAccessTokenCreate(t *testing.T) {
RobiNino marked this conversation as resolved.
Show resolved Hide resolved
initAccessTest(t)
if *tests.JfrogAccessToken == "" {
t.Skip("access token create command only supports authorization with access token, but a token is not provided. Skipping...")
}
testCases := []struct {
RobiNino marked this conversation as resolved.
Show resolved Hide resolved
name string
args []string
shouldExpire bool
expectedExpiry uint
expectedScope string
expectedRefreshable bool
expectedReference bool
}{
{
name: "default",
args: []string{"atc"},
shouldExpire: true,
expectedExpiry: defaultExpiry,
expectedScope: userScope,
expectedRefreshable: false,
expectedReference: false,
},
{
name: "explicit user, no expiry",
args: []string{"atc", auth.ExtractUsernameFromAccessToken(*tests.JfrogAccessToken), "--expiry=0"},
shouldExpire: false,
expectedExpiry: 0,
expectedScope: userScope,
expectedRefreshable: false,
expectedReference: false,
},
{
name: "refreshable, admin",
args: []string{"atc", "--refreshable", "--grant-admin"},
shouldExpire: true,
expectedExpiry: defaultExpiry,
expectedScope: "applied-permissions/admin",
expectedRefreshable: true,
expectedReference: false,
},
{
name: "reference, custom scope, custom expiry",
args: []string{"atc", "--reference", "--scope=system:metrics:r", "--expiry=1234"},
shouldExpire: true,
expectedExpiry: 1234,
expectedScope: "system:metrics:r",
expectedRefreshable: false,
expectedReference: true,
},
{
name: "groups, description",
args: []string{"atc", "--groups=group1,group2", "--description=description"},
shouldExpire: true,
expectedExpiry: defaultExpiry,
expectedScope: "applied-permissions/groups:group1,group2",
expectedRefreshable: false,
expectedReference: false,
},
}

for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
var token auth.CreateTokenResponseData
output := accessCli.RunCliCmdWithOutput(t, test.args...)
assert.NoError(t, json.Unmarshal([]byte(output), &token))

if test.shouldExpire {
assert.EqualValues(t, test.expectedExpiry, *token.ExpiresIn)
} else {
assert.Nil(t, token.ExpiresIn)
}
assert.NotEmpty(t, token.AccessToken)
assert.Equal(t, test.expectedScope, token.Scope)
assertNotEmptyIfExpected(t, test.expectedRefreshable, token.RefreshToken)
assertNotEmptyIfExpected(t, test.expectedReference, token.ReferenceToken)

// Try pinging Artifactory with the new token.
assert.NoError(t, tests.NewJfrogCli(execMain, "jfrog rt",
"--url="+*tests.JfrogUrl+tests.ArtifactoryEndpoint+" --access-token="+token.AccessToken).Exec("ping"))
})
}
}

func assertNotEmptyIfExpected(t *testing.T, expected bool, output string) {
if expected {
assert.NotEmpty(t, output)
} else {
assert.Empty(t, output)
}
}

func initAccessCli() {
if accessCli != nil {
return
}
accessCli = tests.NewJfrogCli(execMain, "jfrog", authenticateAccess())
}

func InitAccessTests() {
initArtifactoryCli()
initAccessCli()
cleanUpOldBuilds()
cleanUpOldRepositories()
cleanUpOldUsers()
tests.AddTimestampToGlobalVars()
createRequiredRepos()
cleanArtifactoryTest()
}

func authenticateAccess() string {
*tests.JfrogUrl = clientUtils.AddTrailingSlashIfNeeded(*tests.JfrogUrl)
accessDetails = &config.ServerDetails{
AccessUrl: *tests.JfrogUrl + tests.AccessEndpoint}

cred := fmt.Sprintf("--url=%s", *tests.JfrogUrl)
if *tests.JfrogAccessToken != "" {
accessDetails.AccessToken = *tests.JfrogAccessToken
cred += fmt.Sprintf(" --access-token=%s", accessDetails.AccessToken)
} else {
accessDetails.User = *tests.JfrogUser
accessDetails.Password = *tests.JfrogPassword
cred += fmt.Sprintf(" --user=%s --password=%s", accessDetails.User, accessDetails.Password)
}
return cred
}
RobiNino marked this conversation as resolved.
Show resolved Hide resolved
23 changes: 10 additions & 13 deletions artifactory/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package artifactory
import (
"errors"
"fmt"
"github.com/jfrog/jfrog-cli/utils/accesstoken"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -860,13 +861,13 @@ func GetCommands() []cli.Command {
{
Name: "access-token-create",
Aliases: []string{"atc"},
Flags: cliutils.GetCommandFlags(cliutils.AccessTokenCreate),
Flags: cliutils.GetCommandFlags(cliutils.ArtifactoryAccessTokenCreate),
Usage: accesstokencreate.GetDescription(),
HelpName: corecommon.CreateUsage("rt atc", accesstokencreate.GetDescription(), accesstokencreate.Usage),
UsageText: accesstokencreate.GetArguments(),
ArgsUsage: common.CreateEnvVars(),
BashComplete: corecommon.CreateBashCompletionFunc(),
Action: accessTokenCreateCmd,
Action: artifactoryAccessTokenCreateCmd,
},
{
Name: "transfer-settings",
Expand Down Expand Up @@ -2199,7 +2200,7 @@ func groupDeleteCmd(c *cli.Context) error {
return commands.Exec(groupDeleteCmd)
}

func accessTokenCreateCmd(c *cli.Context) error {
func artifactoryAccessTokenCreateCmd(c *cli.Context) error {
RobiNino marked this conversation as resolved.
Show resolved Hide resolved
if c.NArg() > 1 {
return cliutils.WrongNumberOfArgumentsHandler(c)
}
Expand All @@ -2208,20 +2209,16 @@ func accessTokenCreateCmd(c *cli.Context) error {
if err != nil {
return err
}
// If the username is provided as an argument, then it is used when creating the token.
// If not, then the configured username (or the value of the --user option) is used.
var userName string
if c.NArg() > 0 {
userName = c.Args().Get(0)
} else {
userName = serverDetails.GetUser()
}
expiry, err := cliutils.GetIntFlagValue(c, "expiry", cliutils.TokenExpiry)

username := accesstoken.GetSubjectUsername(c, serverDetails)
expiry, err := cliutils.GetIntFlagValue(c, cliutils.Expiry, cliutils.ArtifactoryTokenExpiry)
if err != nil {
return err
}
accessTokenCreateCmd := generic.NewAccessTokenCreateCommand()
accessTokenCreateCmd.SetUserName(userName).SetServerDetails(serverDetails).SetRefreshable(c.Bool("refreshable")).SetExpiry(expiry).SetGroups(c.String("groups")).SetAudience(c.String("audience")).SetGrantAdmin(c.Bool("grant-admin"))
accessTokenCreateCmd.SetUserName(username).SetServerDetails(serverDetails).
SetRefreshable(c.Bool(cliutils.Refreshable)).SetExpiry(expiry).SetGroups(c.String(cliutils.Groups)).
SetAudience(c.String(cliutils.Audience)).SetGrantAdmin(c.Bool(cliutils.GrantAdmin))
err = commands.Exec(accessTokenCreateCmd)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion artifactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5190,7 +5190,7 @@ func TestArtifactoryReplicationCreate(t *testing.T) {
cleanArtifactoryTest()
}

func TestAccessTokenCreate(t *testing.T) {
func TestArtifactoryAccessTokenCreate(t *testing.T) {
initArtifactoryTest(t, "")

buffer, _, previousLog := coretests.RedirectLogOutputToBuffer()
Expand Down
8 changes: 4 additions & 4 deletions docs/artifactory/accesstokencreate/help.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package accesstokencreate

var Usage = []string{"rt atc", "rt atc <user name>"}
var Usage = []string{"rt atc", "rt atc <username>"}

func GetDescription() string {
return "Creates an access token. By default an user-scoped token will be created, unless the --groups and/or --grant-admin options are specified."
return "Creates an Artifactory access token. By default an user-scoped token will be created, unless the --groups and/or --grant-admin options are specified."
}

func GetArguments() string {
return ` user name
The user name for which this token is created. If not specified, the token will be created for the current user.`
return ` username
The username for which this token is created. If not specified, the token will be created for the current user.`
}
14 changes: 14 additions & 0 deletions docs/general/token/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package token

var Usage = []string{"atc", "atc <username>"}

func GetDescription() string {
return `Creates an access token.
By default, an user-scoped token will be created.
Administrator may provide the scope explicitly with '--scope', or implicitly with '--groups', '--grant-admin'`
RobiNino marked this conversation as resolved.
Show resolved Hide resolved
}

func GetArguments() string {
return ` username
The username for which this token is created. If not specified, the token will be created for the current user.`
}
Loading
Loading