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

Update auth property format to GALASA_TOKEN and remove client secret from token #211

Merged
merged 2 commits into from
Feb 5, 2024
Merged
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
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ The `--galasahome` command-line flag can override the `GALASA_HOME` environment
Note: If you change this to a non-existent and/or non-initialised folder path, then
you will have to create and re-initialise the folder using the `galasactl local init` command again. That command will respect the `GALASA_HOME` variable and will create the folder and initialise it were it not to exist.

### GALASA_TOKEN
In order to authenticate with a Galasa ecosystem, you will need to create a personal access token from the Galasa web user interface.

Once a personal access token has been created, you can either store the token in the galasactl.properties file within your Galasa home folder, or set the token as an environment variable named `GALASA_TOKEN`.


## Syntax
The syntax is documented in generated documentation [here](docs/generated/galasactl.md)
Expand Down Expand Up @@ -95,17 +100,15 @@ If you wish the generated code to depend upon the very latest/bleeding-edge of g

Before interacting with a Galasa ecosystem using `galasactl`, you must be authenticated with it. The `auth login` command allows you to log in to an ecosystem provided by your `GALASA_BOOTSTRAP` environment variable or through the `--bootstrap` flag.

Prior to running this command, you must have a `galasactl.properties` file in your `GALASA_HOME` directory, which is automatically created when running `galasactl local init`, that contains a set of `auth` properties with the following format:
Prior to running this command, you must have a `galasactl.properties` file in your `GALASA_HOME` directory, which is automatically created when running `galasactl local init`, that contains a `GALASA_TOKEN` property with the following format:

```
GALASA_CLIENT_ID=<a client identifier>
GALASA_SECRET=<a client secret>
GALASA_ACCESS_TOKEN=<a personal access token>
GALASA_TOKEN=<your personal access token>
```

These properties can be retrieved by creating a new personal access token from a Galasa ecosystem's web user interface.
A value for the `GALASA_TOKEN` property can be retrieved by creating a new personal access token from a Galasa ecosystem's web user interface.

If you prefer, these variables can be set as environment variables instead of being read from this file.
If you prefer, this property can be set as an environment variable instead of being read from this file.

On a successful login, a `bearer-token.json` file will be created in your `GALASA_HOME` directory. This file will contain a bearer token that `galasactl` will use to authenticate requests when communicating with a Galasa ecosystem.

Expand Down
1 change: 1 addition & 0 deletions docs/generated/errors-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ The `galasactl` tool can generate the following errors:
- GAL1122E: Authentication property {} is not available, which is needed to connect to the Galasa Ecosystem. It either needs to be in a file '{}' or set as an environment variable.
- GAL1123E: Failed to read 3270 terminal JSON because the content is in the wrong format. Reason: {}
- GAL1124E: Internal Failure. Terminal image could not be encoded into PNG format. Reason: {}
- GAL1125E: Authentication property {} is invalid. Please ensure that it the value is made up of two parts that are separated by a '{}'.
- GAL1225E: Failed to open file '{}' cause: {}. Check that this file exists, and that you have read permissions.
- GAL1226E: Internal failure. Contents of gzip could be read, but not decoded. New gzip reader failed: file: {} error: {}
- GAL1227E: Internal failure. Contents of gzip could not be decoded. {} error: {}
Expand Down
36 changes: 9 additions & 27 deletions pkg/auth/authLogin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func NewAuthServletMock(t *testing.T, status int, mockResponse string) *httptest

requestBodyStr := string(requestBody)
assert.Contains(t, requestBodyStr, "client_id")
assert.Contains(t, requestBodyStr, "secret")
assert.Contains(t, requestBodyStr, "refresh_token")

writer.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -95,12 +94,9 @@ func TestLoginCreatesBearerTokenFileContainingJWT(t *testing.T) {
galasactlPropertiesFilePath := mockGalasaHome.GetNativeFolderPath() + "/galasactl.properties"

mockClientId := "dummyId"
mockSecret := "shhhh"
mockRefreshToken := "abcdefg"
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf(
"GALASA_CLIENT_ID=%s\n"+
"GALASA_SECRET=%s\n"+
"GALASA_ACCESS_TOKEN=%s", mockClientId, mockSecret, mockRefreshToken))
tokenPropertyValue := mockRefreshToken + TOKEN_SEPARATOR + mockClientId
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf("GALASA_TOKEN=%s", tokenPropertyValue))

mockResponse := `{"jwt":"blah"}`
server := NewAuthServletMock(t, 200, mockResponse)
Expand Down Expand Up @@ -130,12 +126,9 @@ func TestLoginWithFailedFileWriteReturnsError(t *testing.T) {
galasactlPropertiesFilePath := mockGalasaHome.GetNativeFolderPath() + "/galasactl.properties"

mockClientId := "dummyId"
mockSecret := "shhhh"
mockRefreshToken := "abcdefg"
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf(
"GALASA_CLIENT_ID=%s\n"+
"GALASA_SECRET=%s\n"+
"GALASA_ACCESS_TOKEN=%s", mockClientId, mockSecret, mockRefreshToken))
tokenPropertyValue := mockRefreshToken + TOKEN_SEPARATOR + mockClientId
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf("GALASA_TOKEN=%s", tokenPropertyValue))

mockFileSystem.VirtualFunction_WriteTextFile = func(path string, contents string) error {
return errors.New("simulating a failed write operation")
Expand Down Expand Up @@ -164,13 +157,9 @@ func TestLoginWithFailedTokenRequestReturnsError(t *testing.T) {
galasactlPropertiesFilePath := mockGalasaHome.GetNativeFolderPath() + "/galasactl.properties"

mockClientId := "dummyId"
mockSecret := "shhhh"
mockRefreshToken := "abcdefg"

mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf(
"GALASA_CLIENT_ID=%s\n"+
"GALASA_SECRET=%s\n"+
"GALASA_ACCESS_TOKEN=%s", mockClientId, mockSecret, mockRefreshToken))
tokenPropertyValue := mockRefreshToken + TOKEN_SEPARATOR + mockClientId
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf("GALASA_TOKEN=%s", tokenPropertyValue))

mockResponse := `{"error":"something went wrong!"}`
server := NewAuthServletMock(t, 500, mockResponse)
Expand All @@ -194,11 +183,7 @@ func TestLoginWithMissingAuthPropertyReturnsError(t *testing.T) {

galasactlPropertiesFilePath := mockGalasaHome.GetNativeFolderPath() + "/galasactl.properties"

mockClientId := "dummyId"
mockSecret := "shhhh"
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf(
"GALASA_CLIENT_ID=%s\n"+
"GALASA_SECRET=%s\n", mockClientId, mockSecret))
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, "unknown.value=blah")

mockResponse := `{"jwt":"blah"}`
server := NewAuthServletMock(t, 200, mockResponse)
Expand Down Expand Up @@ -283,12 +268,9 @@ func TestGetAuthenticatedAPIClientWithUnavailableAPIContinuesWithoutToken(t *tes
galasactlPropertiesFilePath := mockGalasaHome.GetNativeFolderPath() + "/galasactl.properties"

mockClientId := "dummyId"
mockSecret := "shhhh"
mockRefreshToken := "abcdefg"
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf(
"GALASA_CLIENT_ID=%s\n"+
"GALASA_SECRET=%s\n"+
"GALASA_ACCESS_TOKEN=%s", mockClientId, mockSecret, mockRefreshToken))
tokenPropertyValue := mockRefreshToken + TOKEN_SEPARATOR + mockClientId
mockFileSystem.WriteTextFile(galasactlPropertiesFilePath, fmt.Sprintf("GALASA_TOKEN=%s", tokenPropertyValue))

server := NewAuthServletMock(t, 500, "")
defer server.Close()
Expand Down
67 changes: 38 additions & 29 deletions pkg/auth/authProperties.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package auth
import (
"log"
"path/filepath"
"strings"

"github.com/galasa-dev/cli/pkg/files"
"github.com/galasa-dev/cli/pkg/galasaapi"
Expand All @@ -18,40 +19,39 @@ import (
)

const (
CLIENT_ID_PROPERTY = "GALASA_CLIENT_ID"
SECRET_PROPERTY = "GALASA_SECRET"
ACCESS_TOKEN_PROPERTY = "GALASA_ACCESS_TOKEN"
TOKEN_PROPERTY = "GALASA_TOKEN"
TOKEN_SEPARATOR = ":"
)

// Gets authentication properties from the user's galasactl.properties file or from the environment or a mixture.
func GetAuthProperties(fileSystem files.FileSystem, galasaHome utils.GalasaHome, env utils.Environment) (galasaapi.AuthProperties, error) {
var err error = nil
authProperties := galasaapi.NewAuthProperties()

// Work out which file we we want to draw properties from.
galasactlPropertiesFilePath := filepath.Join(galasaHome.GetNativeFolderPath(), "galasactl.properties")

// Get the file-based properties if we can
authProperties, fileAccessErr := getAuthPropertiesFromFile(fileSystem, galasactlPropertiesFilePath, env)
if fileAccessErr != nil {
authProperties = *galasaapi.NewAuthProperties()
}
// Get the file-based token property if we can
tokenProperty, fileAccessErr := getPropertyFromFile(fileSystem, galasactlPropertiesFilePath, env, TOKEN_PROPERTY)

// We now have a structure which may be filled-in with values from the file.
// Over-write those values if there is an environment variable set to do that.
authProperties.SetClientId(getPropertyWithOverride(env, authProperties.GetClientId(), galasactlPropertiesFilePath, CLIENT_ID_PROPERTY))
authProperties.SetRefreshToken(getPropertyWithOverride(env, authProperties.GetRefreshToken(), galasactlPropertiesFilePath, ACCESS_TOKEN_PROPERTY))
authProperties.SetSecret(getPropertyWithOverride(env, authProperties.GetSecret(), galasactlPropertiesFilePath, SECRET_PROPERTY))
// Over-write the token property value if there is an environment variable set to do that.
tokenProperty = getPropertyWithOverride(env, tokenProperty, galasactlPropertiesFilePath, TOKEN_PROPERTY)

// Make sure all the properties have values that we need.
err = checkPropertyIsSet(authProperties.GetClientId(), CLIENT_ID_PROPERTY, galasactlPropertiesFilePath, fileAccessErr)
err = checkPropertyIsSet(tokenProperty, TOKEN_PROPERTY, galasactlPropertiesFilePath, fileAccessErr)
if err == nil {
err = checkPropertyIsSet(authProperties.GetRefreshToken(), ACCESS_TOKEN_PROPERTY, galasactlPropertiesFilePath, fileAccessErr)
var refreshToken string
var clientId string

// Get the authentication properties from the token
refreshToken, clientId, err = extractPropertiesFromToken(tokenProperty)
if err == nil {
err = checkPropertyIsSet(authProperties.GetSecret(), SECRET_PROPERTY, galasactlPropertiesFilePath, fileAccessErr)
authProperties.SetClientId(clientId)
authProperties.SetRefreshToken(refreshToken)
}
}

return authProperties, err
return *authProperties, err
}

func checkPropertyIsSet(propertyValue string, propertyName string, galasactlPropertiesFilePath string, fileAccessErr error) error {
Expand Down Expand Up @@ -84,24 +84,33 @@ func getPropertyWithOverride(env utils.Environment, valueFromFile string, filePa
return value
}

// Gets authentication properties from the user's galasactl.properties file
func getAuthPropertiesFromFile(fileSystem files.FileSystem, galasactlPropertiesFilePath string, env utils.Environment) (galasaapi.AuthProperties, error) {
// Gets a property from the user's galasactl.properties file
func getPropertyFromFile(fileSystem files.FileSystem, galasactlPropertiesFilePath string, env utils.Environment, propertyName string) (string, error) {
var err error = nil
authProperties := galasaapi.NewAuthProperties()

var galasactlProperties props.JavaProperties
galasactlProperties, err = props.ReadPropertiesFile(fileSystem, galasactlPropertiesFilePath)
if err == nil {
if err != nil {
err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_FAILED_TO_READ_FILE, galasactlPropertiesFilePath, err.Error())
}

if err == nil {
authProperties.SetClientId(galasactlProperties[CLIENT_ID_PROPERTY])
authProperties.SetSecret(galasactlProperties[SECRET_PROPERTY])
authProperties.SetRefreshToken(galasactlProperties[ACCESS_TOKEN_PROPERTY])
}
return galasactlProperties[propertyName], err
}

func extractPropertiesFromToken(token string) (string, string, error) {
var err error
var refreshToken string
var clientId string

// The GALASA_TOKEN property should be in the form {GALASA_ACCESS_TOKEN}:{GALASA_CLIENT_ID},
// so it should split into two parts.
tokenParts := strings.Split(token, TOKEN_SEPARATOR)

if len(tokenParts) == 2 && tokenParts[0] != "" && tokenParts[1] != "" {
refreshToken = tokenParts[0]
clientId = tokenParts[1]
} else {
err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_FAILED_TO_READ_FILE, galasactlPropertiesFilePath, err.Error())
err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_BAD_TOKEN_PROPERTY_FORMAT, TOKEN_PROPERTY, TOKEN_SEPARATOR)
}

return *authProperties, err
return refreshToken, clientId, err
}
Loading