Skip to content

Commit

Permalink
Support Platform Access Token
Browse files Browse the repository at this point in the history
  • Loading branch information
RobiNino committed Sep 7, 2023
1 parent 30b7b6a commit 98bc8e3
Show file tree
Hide file tree
Showing 13 changed files with 543 additions and 176 deletions.
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) {
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 {
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
}
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 {
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.`
}
15 changes: 15 additions & 0 deletions docs/general/token/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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 a single of the following options to modify the scope of the token:
'--groups', '--grant-admin' or '--scope.'`
}

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

0 comments on commit 98bc8e3

Please sign in to comment.