mirror of
https://github.com/passbolt/go-passbolt-cli.git
synced 2025-05-12 10:58:21 +00:00
Compare commits
No commits in common. "main" and "v0.1.9" have entirely different histories.
38 changed files with 1090 additions and 1694 deletions
2
.github/workflows/.docs.yml
vendored
2
.github/workflows/.docs.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.21
|
go-version: 1.17
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -o passbolt
|
run: go build -o passbolt
|
||||||
|
|
35
.github/workflows/.release.yml
vendored
35
.github/workflows/.release.yml
vendored
|
@ -13,45 +13,32 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.21
|
go-version: 1.17
|
||||||
-
|
-
|
||||||
name: Generate Man and Completions
|
name: Generate Man and Completions
|
||||||
run: |
|
run: |
|
||||||
mkdir completion
|
mkdir completion
|
||||||
go run *.go completion bash > completion/bash
|
go run main.go completion bash > completion/bash
|
||||||
go run *.go completion zsh > completion/zsh
|
go run main.go completion zsh > completion/zsh
|
||||||
go run *.go completion fish > completion/fish
|
go run main.go completion fish > completion/fish
|
||||||
go run *.go completion powershell > completion/powershell
|
go run main.go completion powershell > completion/powershell
|
||||||
mkdir man
|
mkdir man
|
||||||
go run *.go gendoc --type man
|
go run main.go gendoc --type man
|
||||||
pwd
|
pwd
|
||||||
ls
|
ls
|
||||||
-
|
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
|
||||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
|
||||||
-
|
-
|
||||||
name: Run GoReleaser
|
name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v5
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean ${{ env.flags }}
|
args: release --rm-dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Run Version
|
|
||||||
run: |
|
|
||||||
dist/go-passbolt-cli_linux_amd64_v1/passbolt -v
|
|
||||||
-
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: go-passbolt-cli-artifacts
|
|
||||||
path: dist/
|
|
|
@ -14,7 +14,13 @@ builds:
|
||||||
- arm64
|
- arm64
|
||||||
binary: passbolt
|
binary: passbolt
|
||||||
archives:
|
archives:
|
||||||
- files:
|
- replacements:
|
||||||
|
darwin: Darwin
|
||||||
|
linux: Linux
|
||||||
|
windows: Windows
|
||||||
|
386: i386
|
||||||
|
amd64: x86_64
|
||||||
|
files:
|
||||||
- completion/*
|
- completion/*
|
||||||
- man/*
|
- man/*
|
||||||
format_overrides:
|
format_overrides:
|
||||||
|
@ -25,7 +31,7 @@ release:
|
||||||
header: |
|
header: |
|
||||||
## Release {{ .Tag }} - ({{ .Date }})
|
## Release {{ .Tag }} - ({{ .Date }})
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: Samuel Lorch <sam@soontm.de>
|
- maintainer: Samuel Lorch <sam@lorch.net>
|
||||||
description: A CLI for Passbolt.
|
description: A CLI for Passbolt.
|
||||||
homepage: https://github.com/passbolt/go-passbolt-cli
|
homepage: https://github.com/passbolt/go-passbolt-cli
|
||||||
license: MIT
|
license: MIT
|
||||||
|
@ -42,23 +48,4 @@ nfpms:
|
||||||
- bash_completion
|
- bash_completion
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
brews:
|
|
||||||
- homepage: https://github.com/passbolt/go-passbolt-cli
|
|
||||||
license: "MIT"
|
|
||||||
skip_upload: false
|
|
||||||
description: "A CLI tool to interact with Passbolt, a Open source Password Manager for Teams"
|
|
||||||
directory: Formula
|
|
||||||
install: |
|
|
||||||
bin.install "passbolt"
|
|
||||||
bash_completion.install "completion/bash" => "passbolt"
|
|
||||||
zsh_completion.install "completion/zsh" => "_passbolt"
|
|
||||||
fish_completion.install "completion/fish" => "passbolt.fish"
|
|
||||||
man1.install Dir["man/*"]
|
|
||||||
# ...
|
|
||||||
repository:
|
|
||||||
owner: passbolt
|
|
||||||
name: homebrew-tap
|
|
||||||
token: "{{ .Env.TAP_GITHUB_TOKEN }}"
|
|
||||||
|
|
||||||
|
|
62
README.md
62
README.md
|
@ -1,30 +1,22 @@
|
||||||
# go-passbolt-cli
|
# go-passbolt-cli
|
||||||
A CLI tool to interact with Passbolt, an Open source Password Manager for teams.
|
A CLI tool to interact with Passbolt, a Open source Password Manager for Teams.
|
||||||
|
|
||||||
If you want to do something more complicated: [this Go Module](https://github.com/passbolt/go-passbolt) to Interact with Passbolt from Go might intrest you.
|
If you want to do something more complicated: [this](https://github.com/passbolt/go-passbolt) Go Module to Interact with Passbolt from Go might intrest you.
|
||||||
|
|
||||||
|
|
||||||
Disclaimer: This project is community driven and not associated with Passbolt SA
|
Disclaimer: This project is community driven and not associated with Passbolt SA
|
||||||
# Install
|
# Install
|
||||||
|
|
||||||
## Via Repository (Prefered):
|
## Via Package (Prefered):
|
||||||
[](https://repology.org/project/go:passbolt-cli/versions)
|
Download the Package for your OS and architecture from the Latest Release.
|
||||||
|
|
||||||
Use the package from your Distros Official Repository
|
|
||||||
|
|
||||||
## Via Package:
|
|
||||||
Download the deb/rpm Package for your Distro and architecture from the Latest Release.
|
|
||||||
Install via your Distros Package manager like `dpkg -i`
|
Install via your Distros Package manager like `dpkg -i`
|
||||||
|
|
||||||
## Via Homebrew
|
|
||||||
brew install passbolt/tap/go-passbolt-cli
|
|
||||||
|
|
||||||
## Via Archive:
|
## Via Archive:
|
||||||
Download and Extract the Archive for your OS and architecture from the Latest Release.
|
Download and Extract the Archive for your OS and architecture from the Latest Release.
|
||||||
Note: tab completion and manpages will need to be installed manually.
|
Note: tab completion and manpages will need to be installed manually.
|
||||||
|
|
||||||
## Via Go:
|
## Via Go:
|
||||||
go install github.com/passbolt/go-passbolt-cli@latest
|
go install github.com/passbolt/go-passbolt-cli
|
||||||
Note: this will install the binary as go-passbolt-cli, also tab completion and manpages will be missing.
|
Note: this will install the binary as go-passbolt-cli, also tab completion and manpages will be missing.
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
@ -38,35 +30,35 @@ or
|
||||||
```
|
```
|
||||||
passbolt configure --serverAddress https://passbolt.example.org --userPassword '1234' --userPrivateKey '-----BEGIN PGP PRIVATE KEY BLOCK-----'
|
passbolt configure --serverAddress https://passbolt.example.org --userPassword '1234' --userPrivateKey '-----BEGIN PGP PRIVATE KEY BLOCK-----'
|
||||||
```
|
```
|
||||||
- Setup Environment Variables
|
- Setup Enviroment Variables
|
||||||
- Provide the Flags manually every time
|
- Provide the Flags manually every time
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- You can set the Private Key using the flags `--userPrivateKey` or `--userPrivateKeyFile` where `--userPrivateKey` takes the actual private key and `--userPrivateKeyFile` loads the content of a file as the PrivateKey, `--userPrivateKeyFile` overwrites the value of `--userPrivateKey`.
|
- You can set the Private Key using the flags `--userPrivateKey` or `--userPrivateKeyFile` where `--userPrivateKey` takes the actual private key and `--userPrivateKeyFile` loads the content of a file as the PrivateKey, `--userPrivateKeyFile` overwrites the value of `--userPrivateKey`.
|
||||||
- You can also just store the serverAddress and your Private Key, if your Password is not set it will prompt you for it every time.
|
- You can also just store the serverAddress and your Private Key, if your Password is not set it will prompt you for it every time.
|
||||||
- Passwordless PrivateKeys are unsupported
|
- Passwordless PrivateKeys are unsupported
|
||||||
- MFA settings can also be save permanently this ways
|
- MFA settings can also be save permenantly this ways
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
Generally the Structure of Commands is like this:
|
Generally the Structure of Commands is like this:
|
||||||
```bash
|
```bash
|
||||||
passbolt action entity [arguments]
|
go-passbolt-cli action entity [arguments]
|
||||||
```
|
```
|
||||||
|
|
||||||
Action is the Action you want to perform like Creating, Updating or Deleting an Entity.
|
Action is the Action you want to perform like Creating, Updating or Deleting a Entity.
|
||||||
Entity is a Resource(Password), Folder, User or Group that you want to apply an action to.
|
Entity is a Resource(Password), Folder, User or Group that you want to apply a action to.
|
||||||
|
|
||||||
In Passbolt a Password is usually revert to as a Resource.
|
In Passbolt a Password is usually revert to as a Resource.
|
||||||
|
|
||||||
To Create a Resource you can do this, it will return the ID of the newly created Resource:
|
To Create a Resource you can do this, it will return the ID of the newly created Resource:
|
||||||
```bash
|
```bash
|
||||||
passbolt create resource --name "Test Resource" --password "Strong Password"
|
go-passbolt-cli create resource --name "Test Resource" --password "Strong Password"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then list all users:
|
You can then list all users:
|
||||||
```bash
|
```bash
|
||||||
passbolt list user
|
go-passbolt-cli list user
|
||||||
```
|
```
|
||||||
Note: you can adjust which columns should be listed using the flag `--column` or its short from `-c`, if you want multiple column then you need to specify this flag multiple times.
|
Note: you can adjust which columns should be listed using the flag `--column` or its short from `-c`, if you want multiple column then you need to specify this flag multiple times.
|
||||||
|
|
||||||
|
@ -82,43 +74,25 @@ For sharing we will need to know how we want to share, for that there are these
|
||||||
|
|
||||||
Now that we have a Resource ID, know the ID's of other Users and about know about Permission Types, we can share the Resource with them:
|
Now that we have a Resource ID, know the ID's of other Users and about know about Permission Types, we can share the Resource with them:
|
||||||
```bash
|
```bash
|
||||||
passbolt share resource --id id_of_resource_to_share --type type_of_permission --user id_of_user_to_share_with
|
go-passbolt-cli share resource --id id_of_resource_to_share --type type_of_permission --user id_of_user_to_share_with
|
||||||
```
|
```
|
||||||
Note: you can supply the the users argument multiple times to share with multiple users
|
Note: you can supply the the users argument multiple times to share with multiple users
|
||||||
|
|
||||||
For sharing with groups the `--group` argument exists.
|
For sharing with groups the `--group` argument exists.
|
||||||
|
|
||||||
# MFA
|
# MFA
|
||||||
You can setup MFA also using the configuration sub command, only TOTP is supported, there are multiple modes for MFA: `none`, `interactive-totp` and `noninteractive-totp`.
|
you can setup MFA also using the configuration sub command, only TOTP is supported, there are mulitple modes for MFA: `none`, `interactive-totp` and `noninteractive-totp`.
|
||||||
| Mode | Description |
|
| Mode | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|`none`|just errors if challenged for MFA.
|
|`none`|just errors if challanged for MFA.
|
||||||
|`interactive-totp` | prompts for interactive entry of TOTP Codes.
|
|`interactive-totp` | prompts for interactive entry of TOTP Codes.
|
||||||
|`noninteractive-totp` | automatically generates TOTP Codes when challenged, it requires the `mfaTotpToken` flag to be set to your totp Secret, you can configure the behavior using the `mfaDelay`, `mfaRetrys` and `mfaTotpOffset` flags
|
|`noninteractive-totp` | automatically generates TOTP Codes when challenged, it requires the `totpToken` flag to be set to your totp Secret, you can configure the behavior using the `mfaDelay`, `mfaRetrys` and `totpOffset` flags
|
||||||
|
|
||||||
|
|
||||||
# Server Verification
|
# Server Verification
|
||||||
To enable Server Verification you need to run `passbolt verify` once, after that the server will always be verified if the same config is used
|
to enable Server Verification you need to run `passbolt verify` once, after that the server will always be verified if the same config is used
|
||||||
|
|
||||||
# Scripting
|
|
||||||
For Scripting we have a -j or --json flag to convert the Output for the create, get and list commands to JSON for easier Parsing in Scripts.
|
|
||||||
|
|
||||||
Note: The JSON Output does not cover Error Messages, you can detect Errors by checking if the Exitcode is not 0
|
|
||||||
|
|
||||||
# Exposing Secrets to Subprocesses
|
|
||||||
The `exec` command allows you to execute another command with environment variables that reference secrets stored in Passbolt.
|
|
||||||
Any environment variables containing `passbolt://` references are automatically resolved to their corresponding secret values
|
|
||||||
before the specified command is executed. This ensures that secrets are securely injected into the child process's environment
|
|
||||||
without exposing them to the parent shell.
|
|
||||||
For example:
|
|
||||||
```bash
|
|
||||||
export GITHUB_TOKEN=passbolt://<PASSBOLT_RESOURCE_ID_HERE>
|
|
||||||
passbolt exec -- gh auth login
|
|
||||||
```
|
|
||||||
|
|
||||||
This would resolve the passbolt:// reference in GITHUB_TOKEN to its actual secret value and pass it to the gh process.
|
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
Usage for all Subcommands is [here](https://github.com/passbolt/go-passbolt-cli/wiki/passbolt).
|
Usage for all Subcommands is [here](https://github.com/passbolt/go-passbolt-cli/wiki/go-passbolt-cli).
|
||||||
And is also available via `man passbolt`
|
And is also available via `man passbolt`
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ var createCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(createCmd)
|
rootCmd.AddCommand(createCmd)
|
||||||
createCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
|
||||||
createCmd.AddCommand(resource.ResourceCreateCmd)
|
createCmd.AddCommand(resource.ResourceCreateCmd)
|
||||||
createCmd.AddCommand(folder.FolderCreateCmd)
|
createCmd.AddCommand(folder.FolderCreateCmd)
|
||||||
createCmd.AddCommand(group.GroupCreateCmd)
|
createCmd.AddCommand(group.GroupCreateCmd)
|
||||||
|
|
103
cmd/exec.go
103
cmd/exec.go
|
@ -1,103 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
|
||||||
"github.com/passbolt/go-passbolt/api"
|
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const PassboltPrefix = "passbolt://"
|
|
||||||
|
|
||||||
// execCmd represents the exec command
|
|
||||||
var execCmd = &cobra.Command{
|
|
||||||
Use: "exec -- command [args...]",
|
|
||||||
Short: "Run a command with secrets injected into the environment.",
|
|
||||||
Long: `The command allows you to execute another command with environment variables that reference secrets stored in Passbolt.
|
|
||||||
Any environment variables containing passbolt:// references are automatically resolved to their corresponding secret values
|
|
||||||
before the specified command is executed. This ensures that secrets are securely injected into the child process's environment
|
|
||||||
without exposing them to the parent shell.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
export GITHUB_TOKEN=passbolt://<PASSBOLT_RESOURCE_ID_HERE>
|
|
||||||
passbolt exec -- gh auth login
|
|
||||||
|
|
||||||
This would resolve the passbolt:// reference in GITHUB_TOKEN to its actual secret value and pass it to the gh process.
|
|
||||||
`,
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
RunE: execAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(execCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func execAction(_ *cobra.Command, args []string) error {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
client, err := util.GetClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Creating client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
envVars, err := resolveEnvironmentSecrets(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Resolving secrets: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.Logout(ctx); err != nil {
|
|
||||||
return fmt.Errorf("Logging out client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
subCmd := exec.Command(args[0], args[1:]...)
|
|
||||||
subCmd.Stdin = os.Stdin
|
|
||||||
subCmd.Stdout = os.Stdout
|
|
||||||
subCmd.Stderr = os.Stderr
|
|
||||||
subCmd.Env = envVars
|
|
||||||
|
|
||||||
if err = subCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("Running command: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveEnvironmentSecrets(ctx context.Context, client *api.Client) ([]string, error) {
|
|
||||||
envVars := os.Environ()
|
|
||||||
|
|
||||||
for i, envVar := range envVars {
|
|
||||||
splitIndex := strings.Index(envVar, "=")
|
|
||||||
if splitIndex == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
key := envVar[:splitIndex]
|
|
||||||
value := envVar[splitIndex+1:]
|
|
||||||
|
|
||||||
if !strings.HasPrefix(value, PassboltPrefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceId := strings.TrimPrefix(value, PassboltPrefix)
|
|
||||||
_, _, _, _, secret, _, err := helper.GetResource(ctx, client, resourceId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Getting resource: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
envVars[i] = key + "=" + secret
|
|
||||||
|
|
||||||
if viper.GetBool("debug") {
|
|
||||||
fmt.Fprintf(os.Stdout, "%v env var populated with resource id %v\n", key, resourceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return envVars, nil
|
|
||||||
}
|
|
|
@ -18,7 +18,6 @@ var getCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(getCmd)
|
rootCmd.AddCommand(getCmd)
|
||||||
getCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
|
||||||
getCmd.AddCommand(resource.ResourceGetCmd)
|
getCmd.AddCommand(resource.ResourceGetCmd)
|
||||||
getCmd.AddCommand(folder.FolderGetCmd)
|
getCmd.AddCommand(folder.FolderGetCmd)
|
||||||
getCmd.AddCommand(group.GroupGetCmd)
|
getCmd.AddCommand(group.GroupGetCmd)
|
||||||
|
|
|
@ -18,13 +18,6 @@ var listCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(listCmd)
|
rootCmd.AddCommand(listCmd)
|
||||||
listCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
|
||||||
listCmd.PersistentFlags().String("filter", "",
|
|
||||||
"Define a CEl expression as filter for any list commands. In the expression, all available columns of subcommand can be used (see -c/--column).\n"+
|
|
||||||
"See also CEl specifications under https://github.com/google/cel-spec.\n"+
|
|
||||||
"Examples:\n"+
|
|
||||||
"\t--filter '(Name == \"SomeName\" || matches(Name, \"RegExpr\")) && URI.startsWith(\"https://auth.\")'\n"+
|
|
||||||
"\t--filter 'Username == \"User\" && CreatedTimestamp > timestamp(\"2022-06-10T00:00:00.000-00:00\")'")
|
|
||||||
listCmd.AddCommand(resource.ResourceListCmd)
|
listCmd.AddCommand(resource.ResourceListCmd)
|
||||||
listCmd.AddCommand(folder.FolderListCmd)
|
listCmd.AddCommand(folder.FolderListCmd)
|
||||||
listCmd.AddCommand(group.GroupListCmd)
|
listCmd.AddCommand(group.GroupListCmd)
|
||||||
|
|
66
cmd/root.go
66
cmd/root.go
|
@ -2,6 +2,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
@ -47,24 +48,11 @@ func init() {
|
||||||
rootCmd.PersistentFlags().String("userPrivateKeyFile", "", "Passbolt User Private Key File, if set then the userPrivateKey will be Overwritten with the File Content")
|
rootCmd.PersistentFlags().String("userPrivateKeyFile", "", "Passbolt User Private Key File, if set then the userPrivateKey will be Overwritten with the File Content")
|
||||||
rootCmd.PersistentFlags().String("userPassword", "", "Passbolt User Password")
|
rootCmd.PersistentFlags().String("userPassword", "", "Passbolt User Password")
|
||||||
rootCmd.PersistentFlags().String("mfaMode", "interactive-totp", "How to Handle MFA, the following Modes exist: none, interactive-totp and noninteractive-totp")
|
rootCmd.PersistentFlags().String("mfaMode", "interactive-totp", "How to Handle MFA, the following Modes exist: none, interactive-totp and noninteractive-totp")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().String("totpToken", "", "Token to generate TOTP's, only used in nointeractive-totp mode")
|
rootCmd.PersistentFlags().String("totpToken", "", "Token to generate TOTP's, only used in nointeractive-totp mode")
|
||||||
rootCmd.PersistentFlags().MarkDeprecated("totpToken", "use --mfaTotpToken instead")
|
|
||||||
rootCmd.PersistentFlags().String("mfaTotpToken", "", "Token to generate TOTP's, only used in nointeractive-totp mode")
|
|
||||||
|
|
||||||
rootCmd.PersistentFlags().Duration("totpOffset", time.Duration(0), "TOTP Generation offset only used in noninteractive-totp mode")
|
rootCmd.PersistentFlags().Duration("totpOffset", time.Duration(0), "TOTP Generation offset only used in noninteractive-totp mode")
|
||||||
rootCmd.PersistentFlags().MarkDeprecated("totpOffset", "use --mfaTotpOffset instead")
|
|
||||||
rootCmd.PersistentFlags().Duration("mfaTotpOffset", time.Duration(0), "TOTP Generation offset only used in noninteractive-totp mode")
|
|
||||||
|
|
||||||
rootCmd.PersistentFlags().Uint("mfaRetrys", 3, "How often to retry TOTP Auth, only used in nointeractive modes")
|
rootCmd.PersistentFlags().Uint("mfaRetrys", 3, "How often to retry TOTP Auth, only used in nointeractive modes")
|
||||||
rootCmd.PersistentFlags().Duration("mfaDelay", time.Second*10, "Delay between MFA Attempts, only used in noninteractive modes")
|
rootCmd.PersistentFlags().Duration("mfaDelay", time.Second*10, "Delay between MFA Attempts, only used in noninteractive modes")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().Bool("tlsSkipVerify", false, "Allow servers with self-signed certificates")
|
|
||||||
rootCmd.PersistentFlags().String("tlsClientPrivateKeyFile", "", "Client private key path for mtls")
|
|
||||||
rootCmd.PersistentFlags().String("tlsClientCertFile", "", "Client certificate path for mtls")
|
|
||||||
rootCmd.PersistentFlags().String("tlsClientPrivateKey", "", "Client private key for mtls")
|
|
||||||
rootCmd.PersistentFlags().String("tlsClientCert", "", "Client certificate for mtls")
|
|
||||||
|
|
||||||
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
|
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
|
||||||
viper.BindPFlag("timeout", rootCmd.PersistentFlags().Lookup("timeout"))
|
viper.BindPFlag("timeout", rootCmd.PersistentFlags().Lookup("timeout"))
|
||||||
viper.BindPFlag("serverAddress", rootCmd.PersistentFlags().Lookup("serverAddress"))
|
viper.BindPFlag("serverAddress", rootCmd.PersistentFlags().Lookup("serverAddress"))
|
||||||
|
@ -72,27 +60,9 @@ func init() {
|
||||||
viper.BindPFlag("userPassword", rootCmd.PersistentFlags().Lookup("userPassword"))
|
viper.BindPFlag("userPassword", rootCmd.PersistentFlags().Lookup("userPassword"))
|
||||||
viper.BindPFlag("mfaMode", rootCmd.PersistentFlags().Lookup("mfaMode"))
|
viper.BindPFlag("mfaMode", rootCmd.PersistentFlags().Lookup("mfaMode"))
|
||||||
viper.BindPFlag("totpToken", rootCmd.PersistentFlags().Lookup("totpToken"))
|
viper.BindPFlag("totpToken", rootCmd.PersistentFlags().Lookup("totpToken"))
|
||||||
viper.BindPFlag("mfaTotpToken", rootCmd.PersistentFlags().Lookup("mfaTotpToken"))
|
|
||||||
viper.BindPFlag("totpOffset", rootCmd.PersistentFlags().Lookup("totpOffset"))
|
viper.BindPFlag("totpOffset", rootCmd.PersistentFlags().Lookup("totpOffset"))
|
||||||
viper.BindPFlag("mfaTotpOffset", rootCmd.PersistentFlags().Lookup("mfaTotpOffset"))
|
|
||||||
viper.BindPFlag("mfaRetrys", rootCmd.PersistentFlags().Lookup("mfaRetrys"))
|
viper.BindPFlag("mfaRetrys", rootCmd.PersistentFlags().Lookup("mfaRetrys"))
|
||||||
viper.BindPFlag("mfaDelay", rootCmd.PersistentFlags().Lookup("mfaDelay"))
|
viper.BindPFlag("mfaDelay", rootCmd.PersistentFlags().Lookup("mfaDelay"))
|
||||||
|
|
||||||
viper.BindPFlag("tlsSkipVerify", rootCmd.PersistentFlags().Lookup("tlsSkipVerify"))
|
|
||||||
viper.BindPFlag("tlsClientCert", rootCmd.PersistentFlags().Lookup("tlsClientCert"))
|
|
||||||
viper.BindPFlag("tlsClientPrivateKey", rootCmd.PersistentFlags().Lookup("tlsClientPrivateKey"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileToContent(file, contentFlag string) {
|
|
||||||
if viper.GetBool("debug") {
|
|
||||||
fmt.Fprintln(os.Stderr, "Loading file:", file)
|
|
||||||
}
|
|
||||||
content, err := os.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error Loading File: ", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
viper.Set(contentFlag, string(content))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
// initConfig reads in config file and ENV variables if set.
|
||||||
|
@ -128,32 +98,16 @@ func initConfig() {
|
||||||
// Read in Private Key from File if userprivatekeyfile is set
|
// Read in Private Key from File if userprivatekeyfile is set
|
||||||
userprivatekeyfile, err := rootCmd.PersistentFlags().GetString("userPrivateKeyFile")
|
userprivatekeyfile, err := rootCmd.PersistentFlags().GetString("userPrivateKeyFile")
|
||||||
if err == nil && userprivatekeyfile != "" {
|
if err == nil && userprivatekeyfile != "" {
|
||||||
fileToContent(userprivatekeyfile, "userPrivateKey")
|
if viper.GetBool("debug") {
|
||||||
|
fmt.Fprintln(os.Stderr, "Loading Private Key from File:", userprivatekeyfile)
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(userprivatekeyfile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error Loading Private Key from File: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
viper.Set("userprivatekey", string(content))
|
||||||
} else if err != nil && viper.GetBool("debug") {
|
} else if err != nil && viper.GetBool("debug") {
|
||||||
fmt.Fprintln(os.Stderr, "Getting Private Key File Flag:", err)
|
fmt.Fprintln(os.Stderr, "Getting Private Key File Flag:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read in Client Certificate Private Key from File if tlsClientPrivateKeyFile is set
|
|
||||||
tlsclientprivatekeyfile, err := rootCmd.PersistentFlags().GetString("tlsClientPrivateKeyFile")
|
|
||||||
if err == nil && tlsclientprivatekeyfile != "" {
|
|
||||||
fileToContent(tlsclientprivatekeyfile, "tlsClientPrivateKey")
|
|
||||||
} else if err != nil && viper.GetBool("debug") {
|
|
||||||
fmt.Fprintln(os.Stderr, "Getting Client Certificate Private key File Flag:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read in Client Certificate from File if tlsClientCertFile is set
|
|
||||||
tlsclientcertfile, err := rootCmd.PersistentFlags().GetString("tlsClientCertFile")
|
|
||||||
if err == nil && tlsclientcertfile != "" {
|
|
||||||
fileToContent(tlsclientcertfile, "tlsClientCert")
|
|
||||||
} else if err != nil && viper.GetBool("debug") {
|
|
||||||
fmt.Fprintln(os.Stderr, "Getting Client Certificate File Flag:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetVersionInfo(version, commit, date string, dirty bool) {
|
|
||||||
v := fmt.Sprintf("%s (Built on %s from Git SHA %s)", version, date, commit)
|
|
||||||
if dirty {
|
|
||||||
v = v + " dirty"
|
|
||||||
}
|
|
||||||
rootCmd.Version = v
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// verifyCMD represents the verify command
|
// verifyCMD represents the verify command
|
||||||
|
@ -32,20 +34,17 @@ var verifyCMD = &cobra.Command{
|
||||||
|
|
||||||
userPassword := viper.GetString("userPassword")
|
userPassword := viper.GetString("userPassword")
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
pw, err := util.ReadPassword("Enter Password:")
|
fmt.Print("Enter Password:")
|
||||||
|
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return fmt.Errorf("Reading Password: %w", err)
|
return fmt.Errorf("Reading Password: %w", err)
|
||||||
}
|
}
|
||||||
userPassword = pw
|
userPassword = string(bytepw)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient, err := util.GetHttpClient()
|
client, err := api.NewClient(nil, "", serverAddress, userPrivateKey, userPassword)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Creating Client: %w", err)
|
return fmt.Errorf("Creating Client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package folder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
|
@ -34,10 +33,6 @@ func FolderCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -58,18 +53,6 @@ func FolderCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating Folder: %w", err)
|
return fmt.Errorf("Creating Folder: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
fmt.Printf("FolderID: %v\n", id)
|
||||||
jsonId, err := json.MarshalIndent(
|
|
||||||
map[string]string{"id": id},
|
|
||||||
"",
|
|
||||||
" ",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Marshalling Json: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonId))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("FolderID: %v\n", id)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
package folder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
|
||||||
"github.com/passbolt/go-passbolt/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Environments for CEl
|
|
||||||
var celEnvOptions = []cel.EnvOption{
|
|
||||||
cel.Variable("ID", cel.StringType),
|
|
||||||
cel.Variable("FolderParentID", cel.StringType),
|
|
||||||
cel.Variable("Name", cel.StringType),
|
|
||||||
cel.Variable("CreatedTimestamp", cel.TimestampType),
|
|
||||||
cel.Variable("ModifiedTimestamp", cel.TimestampType),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters the slice folders by invoke CEL program for each folder
|
|
||||||
func filterFolders(folders *[]api.Folder, celCmd string, ctx context.Context) ([]api.Folder, error) {
|
|
||||||
if celCmd == "" {
|
|
||||||
return *folders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
program, err := util.InitCELProgram(celCmd, celEnvOptions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredFolders := []api.Folder{}
|
|
||||||
for _, folder := range *folders {
|
|
||||||
val, _, err := (*program).ContextEval(ctx, map[string]any{
|
|
||||||
"ID": folder.ID,
|
|
||||||
"FolderParentID": folder.FolderParentID,
|
|
||||||
"Name": folder.Name,
|
|
||||||
"CreatedTimestamp": folder.Created.Time,
|
|
||||||
"ModifiedTimestamp": folder.Modified.Time,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.Value() == true {
|
|
||||||
filteredFolders = append(filteredFolders, folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filteredFolders) == 0 {
|
|
||||||
return nil, fmt.Errorf("No such folders found with filter %v!", celCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredFolders, nil
|
|
||||||
}
|
|
|
@ -2,11 +2,11 @@ package folder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,10 +29,6 @@ func FolderGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -43,22 +39,15 @@ func FolderGet(cmd *cobra.Command, args []string) error {
|
||||||
defer client.Logout(context.TODO())
|
defer client.Logout(context.TODO())
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
folder, err := client.GetFolder(ctx, id, nil)
|
folderParentID, name, err := helper.GetFolder(
|
||||||
|
ctx,
|
||||||
|
client,
|
||||||
|
id,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting Folder: %w", err)
|
return fmt.Errorf("Getting Folder: %w", err)
|
||||||
}
|
}
|
||||||
if jsonOutput {
|
fmt.Printf("FolderParentID: %v\n", folderParentID)
|
||||||
jsonGroup, err := json.MarshalIndent(FolderJsonOutput{
|
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
||||||
FolderParentID: &folder.FolderParentID,
|
|
||||||
Name: &folder.Name,
|
|
||||||
}, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonGroup))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("FolderParentID: %v\n", folder.FolderParentID)
|
|
||||||
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(folder.Name))
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package folder
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type FolderJsonOutput struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
FolderParentID *string `json:"folder_parent_id,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
CreatedTimestamp *time.Time `json:"created_timestamp,omitempty"`
|
|
||||||
ModifiedTimestamp *time.Time `json:"modified_timestamp,omitempty"`
|
|
||||||
}
|
|
|
@ -2,12 +2,10 @@ package folder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -28,7 +26,7 @@ func init() {
|
||||||
FolderListCmd.Flags().StringP("search", "s", "", "Folders that have this in the Name")
|
FolderListCmd.Flags().StringP("search", "s", "", "Folders that have this in the Name")
|
||||||
FolderListCmd.Flags().StringArrayP("folder", "f", []string{}, "Folders that are in this Folder")
|
FolderListCmd.Flags().StringArrayP("folder", "f", []string{}, "Folders that are in this Folder")
|
||||||
FolderListCmd.Flags().StringArrayP("group", "g", []string{}, "Folders that are shared with group")
|
FolderListCmd.Flags().StringArrayP("group", "g", []string{}, "Folders that are shared with group")
|
||||||
FolderListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, CreatedTimestamp, ModifiedTimestamp")
|
FolderListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name"}, "Columns to return, possible Columns:\nID, FolderParentID, Name")
|
||||||
}
|
}
|
||||||
|
|
||||||
func FolderList(cmd *cobra.Command, args []string) error {
|
func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -47,14 +45,6 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to Specify atleast one column to return")
|
return fmt.Errorf("You need to Specify atleast one column to return")
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
celFilter, err := cmd.Flags().GetString("filter")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
@ -73,53 +63,26 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing Folder: %w", err)
|
return fmt.Errorf("Listing Folder: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
folders, err = filterFolders(&folders, celFilter, ctx)
|
data := pterm.TableData{columns}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonOutput {
|
for _, folder := range folders {
|
||||||
outputFolders := []FolderJsonOutput{}
|
entry := make([]string, len(columns))
|
||||||
for i := range folders {
|
for i := range columns {
|
||||||
outputFolders = append(outputFolders, FolderJsonOutput{
|
switch strings.ToLower(columns[i]) {
|
||||||
ID: &folders[i].ID,
|
case "id":
|
||||||
FolderParentID: &folders[i].FolderParentID,
|
entry[i] = folder.ID
|
||||||
Name: &folders[i].Name,
|
case "folderparentid":
|
||||||
CreatedTimestamp: &folders[i].Created.Time,
|
entry[i] = folder.FolderParentID
|
||||||
ModifiedTimestamp: &folders[i].Modified.Time,
|
case "name":
|
||||||
})
|
entry[i] = shellescape.StripUnsafe(folder.Name)
|
||||||
}
|
default:
|
||||||
jsonFolders, err := json.MarshalIndent(outputFolders, "", " ")
|
cmd.SilenceUsage = false
|
||||||
if err != nil {
|
return fmt.Errorf("Unknown Column: %v", columns[i])
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonFolders))
|
|
||||||
} else {
|
|
||||||
data := pterm.TableData{columns}
|
|
||||||
|
|
||||||
for _, folder := range folders {
|
|
||||||
entry := make([]string, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
switch strings.ToLower(columns[i]) {
|
|
||||||
case "id":
|
|
||||||
entry[i] = folder.ID
|
|
||||||
case "folderparentid":
|
|
||||||
entry[i] = folder.FolderParentID
|
|
||||||
case "name":
|
|
||||||
entry[i] = shellescape.StripUnsafe(folder.Name)
|
|
||||||
case "createdtimestamp":
|
|
||||||
entry[i] = folder.Created.Format(time.RFC3339)
|
|
||||||
case "modifiedtimestamp":
|
|
||||||
entry[i] = folder.Modified.Format(time.RFC3339)
|
|
||||||
default:
|
|
||||||
cmd.SilenceUsage = false
|
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data = append(data, entry)
|
|
||||||
}
|
}
|
||||||
|
data = append(data, entry)
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
76
go.mod
76
go.mod
|
@ -1,67 +1,23 @@
|
||||||
module github.com/passbolt/go-passbolt-cli
|
module github.com/passbolt/go-passbolt-cli
|
||||||
|
|
||||||
go 1.23.0
|
go 1.16
|
||||||
|
|
||||||
toolchain go1.23.6
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
al.essio.dev/pkg/shellescape v1.5.1
|
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
|
||||||
github.com/google/cel-go v0.24.1
|
github.com/ProtonMail/gopenpgp/v2 v2.4.6 // indirect
|
||||||
github.com/passbolt/go-passbolt v0.7.2
|
github.com/alessio/shellescape v1.4.1
|
||||||
github.com/pterm/pterm v0.12.80
|
github.com/gookit/color v1.5.0 // indirect
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/passbolt/go-passbolt v0.5.7
|
||||||
github.com/tobischo/gokeepasslib/v3 v3.6.1
|
github.com/pterm/pterm v0.12.41
|
||||||
golang.org/x/term v0.29.0
|
github.com/spf13/afero v1.8.2 // indirect
|
||||||
)
|
github.com/spf13/cobra v1.4.0
|
||||||
|
github.com/spf13/viper v1.10.1
|
||||||
require (
|
github.com/tobischo/gokeepasslib/v3 v3.2.4
|
||||||
atomicgo.dev/cursor v0.2.0 // indirect
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||||
atomicgo.dev/schedule v0.1.0 // indirect
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
||||||
cel.dev/expr v0.21.2 // indirect
|
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.8.3 // indirect
|
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
|
||||||
github.com/cloudflare/circl v1.6.0 // indirect
|
|
||||||
github.com/containerd/console v1.0.4 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/gookit/color v1.5.4 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
|
||||||
github.com/magiconair/properties v1.8.9 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
|
||||||
github.com/santhosh-tekuri/jsonschema v1.2.4 // indirect
|
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
|
||||||
github.com/spf13/afero v1.12.0 // indirect
|
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
|
||||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
|
||||||
github.com/tobischo/argon2 v0.1.0 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
golang.org/x/crypto v0.35.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 // indirect
|
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
|
||||||
golang.org/x/text v0.22.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// replace github.com/passbolt/go-passbolt => ../go-passbolt
|
// replace github.com/passbolt/go-passbolt => ../go-passbolt
|
||||||
|
|
|
@ -2,7 +2,6 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
|
@ -41,10 +40,6 @@ func GroupCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ops := []helper.GroupMembershipOperation{}
|
ops := []helper.GroupMembershipOperation{}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
|
@ -79,18 +74,6 @@ func GroupCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating Group: %w", err)
|
return fmt.Errorf("Creating Group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
fmt.Printf("GroupID: %v\n", id)
|
||||||
jsonId, err := json.MarshalIndent(
|
|
||||||
map[string]string{"id": id},
|
|
||||||
"",
|
|
||||||
" ",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Marshalling Json: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonId))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("GroupID: %v\n", id)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
package group
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
|
||||||
"github.com/passbolt/go-passbolt/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Environments for CEl
|
|
||||||
var celEnvOptions = []cel.EnvOption{
|
|
||||||
cel.Variable("ID", cel.StringType),
|
|
||||||
cel.Variable("Name", cel.StringType),
|
|
||||||
cel.Variable("CreatedTimestamp", cel.TimestampType),
|
|
||||||
cel.Variable("ModifiedTimestamp", cel.TimestampType),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters the slice groups by invoke CEL program for each group
|
|
||||||
func filterGroups(groups *[]api.Group, celCmd string, ctx context.Context) ([]api.Group, error) {
|
|
||||||
if celCmd == "" {
|
|
||||||
return *groups, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
program, err := util.InitCELProgram(celCmd, celEnvOptions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredGroups := []api.Group{}
|
|
||||||
for _, group := range *groups {
|
|
||||||
val, _, err := (*program).ContextEval(ctx, map[string]any{
|
|
||||||
"ID": group.ID,
|
|
||||||
"Name": group.Name,
|
|
||||||
"CreatedTimestamp": group.Created.Time,
|
|
||||||
"ModifiedTimestamp": group.Modified.Time,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.Value() == true {
|
|
||||||
filteredGroups = append(filteredGroups, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filteredGroups) == 0 {
|
|
||||||
return nil, fmt.Errorf("No such groups found with filter %v!", celCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredGroups, nil
|
|
||||||
}
|
|
79
group/get.go
79
group/get.go
|
@ -2,11 +2,10 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
@ -38,10 +37,6 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -60,58 +55,34 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting Group: %w", err)
|
return fmt.Errorf("Getting Group: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Name: %v\n", name)
|
||||||
|
// Print Memberships
|
||||||
|
if len(columns) != 0 {
|
||||||
|
data := pterm.TableData{columns}
|
||||||
|
|
||||||
if jsonOutput {
|
for _, membership := range memberships {
|
||||||
groupUserMemberships := []GroupUserMembershipJsonOutput{}
|
entry := make([]string, len(columns))
|
||||||
for i := range memberships {
|
for i := range columns {
|
||||||
groupUserMemberships = append(groupUserMemberships, GroupUserMembershipJsonOutput{
|
switch strings.ToLower(columns[i]) {
|
||||||
ID: &memberships[i].UserID,
|
case "userid":
|
||||||
Username: &memberships[i].Username,
|
entry[i] = membership.UserID
|
||||||
FirstName: &memberships[i].UserFirstName,
|
case "isgroupmanager":
|
||||||
LastName: &memberships[i].UserLastName,
|
entry[i] = fmt.Sprint(membership.IsGroupManager)
|
||||||
IsGroupManager: &memberships[i].IsGroupManager,
|
case "username":
|
||||||
})
|
entry[i] = shellescape.StripUnsafe(membership.Username)
|
||||||
}
|
case "userfirstname":
|
||||||
|
entry[i] = shellescape.StripUnsafe(membership.UserFirstName)
|
||||||
jsonGroup, err := json.MarshalIndent(GroupJsonOutput{
|
case "userlastname":
|
||||||
Name: &name,
|
entry[i] = shellescape.StripUnsafe(membership.UserLastName)
|
||||||
Users: groupUserMemberships,
|
default:
|
||||||
}, "", " ")
|
cmd.SilenceUsage = false
|
||||||
if err != nil {
|
return fmt.Errorf("Unknown Column: %v", columns[i])
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonGroup))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Name: %v\n", name)
|
|
||||||
// Print Memberships
|
|
||||||
if len(columns) != 0 {
|
|
||||||
data := pterm.TableData{columns}
|
|
||||||
|
|
||||||
for _, membership := range memberships {
|
|
||||||
entry := make([]string, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
switch strings.ToLower(columns[i]) {
|
|
||||||
case "userid":
|
|
||||||
entry[i] = membership.UserID
|
|
||||||
case "isgroupmanager":
|
|
||||||
entry[i] = fmt.Sprint(membership.IsGroupManager)
|
|
||||||
case "username":
|
|
||||||
entry[i] = shellescape.StripUnsafe(membership.Username)
|
|
||||||
case "userfirstname":
|
|
||||||
entry[i] = shellescape.StripUnsafe(membership.UserFirstName)
|
|
||||||
case "userlastname":
|
|
||||||
entry[i] = shellescape.StripUnsafe(membership.UserLastName)
|
|
||||||
default:
|
|
||||||
cmd.SilenceUsage = false
|
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data = append(data, entry)
|
|
||||||
}
|
}
|
||||||
|
data = append(data, entry)
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package group
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type GroupJsonOutput struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
Users []GroupUserMembershipJsonOutput `json:"users,omitempty"`
|
|
||||||
CreatedTimestamp *time.Time `json:"created_timestamp,omitempty"`
|
|
||||||
ModifiedTimestamp *time.Time `json:"modified_timestamp,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupUserMembershipJsonOutput struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
Username *string `json:"username,omitempty"`
|
|
||||||
FirstName *string `json:"first_name,omitempty"`
|
|
||||||
LastName *string `json:"last_name,omitempty"`
|
|
||||||
IsGroupManager *bool `json:"is_group_manager,omitempty"`
|
|
||||||
}
|
|
|
@ -2,12 +2,10 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -28,7 +26,7 @@ func init() {
|
||||||
GroupListCmd.Flags().StringArrayP("user", "u", []string{}, "Groups that are shared with group")
|
GroupListCmd.Flags().StringArrayP("user", "u", []string{}, "Groups that are shared with group")
|
||||||
GroupListCmd.Flags().StringArrayP("manager", "m", []string{}, "Groups that are in folder")
|
GroupListCmd.Flags().StringArrayP("manager", "m", []string{}, "Groups that are in folder")
|
||||||
|
|
||||||
GroupListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Name"}, "Columns to return, possible Columns:\nID, Name, CreatedTimestamp, ModifiedTimestamp")
|
GroupListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Name"}, "Columns to return, possible Columns:\nID, Name")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GroupList(cmd *cobra.Command, args []string) error {
|
func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -47,14 +45,6 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
celFilter, err := cmd.Flags().GetString("filter")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -65,7 +55,7 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
defer client.Logout(context.TODO())
|
defer client.Logout(context.TODO())
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
groups, err := client.GetGroups(ctx, &api.GetGroupsOptions{
|
resources, err := client.GetGroups(ctx, &api.GetGroupsOptions{
|
||||||
FilterHasUsers: users,
|
FilterHasUsers: users,
|
||||||
FilterHasManagers: managers,
|
FilterHasManagers: managers,
|
||||||
})
|
})
|
||||||
|
@ -73,50 +63,24 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing Group: %w", err)
|
return fmt.Errorf("Listing Group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
groups, err = filterGroups(&groups, celFilter, ctx)
|
data := pterm.TableData{columns}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonOutput {
|
for _, resource := range resources {
|
||||||
outputGroups := []GroupJsonOutput{}
|
entry := make([]string, len(columns))
|
||||||
for i := range groups {
|
for i := range columns {
|
||||||
outputGroups = append(outputGroups, GroupJsonOutput{
|
switch strings.ToLower(columns[i]) {
|
||||||
ID: &groups[i].ID,
|
case "id":
|
||||||
Name: &groups[i].Name,
|
entry[i] = resource.ID
|
||||||
CreatedTimestamp: &groups[i].Created.Time,
|
case "name":
|
||||||
ModifiedTimestamp: &groups[i].Modified.Time,
|
entry[i] = shellescape.StripUnsafe(resource.Name)
|
||||||
})
|
default:
|
||||||
}
|
cmd.SilenceUsage = false
|
||||||
jsonGroups, err := json.MarshalIndent(outputGroups, "", " ")
|
return fmt.Errorf("Unknown Column: %v", columns[i])
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonGroups))
|
|
||||||
} else {
|
|
||||||
data := pterm.TableData{columns}
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
entry := make([]string, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
switch strings.ToLower(columns[i]) {
|
|
||||||
case "id":
|
|
||||||
entry[i] = group.ID
|
|
||||||
case "name":
|
|
||||||
entry[i] = shellescape.StripUnsafe(group.Name)
|
|
||||||
case "createdtimestamp":
|
|
||||||
entry[i] = group.Created.Format(time.RFC3339)
|
|
||||||
case "modifiedtimestamp":
|
|
||||||
entry[i] = group.Modified.Format(time.RFC3339)
|
|
||||||
default:
|
|
||||||
cmd.SilenceUsage = false
|
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data = append(data, entry)
|
|
||||||
}
|
}
|
||||||
|
data = append(data, entry)
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,9 @@ package keepass
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"syscall"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
|
@ -17,6 +13,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tobischo/gokeepasslib/v3"
|
"github.com/tobischo/gokeepasslib/v3"
|
||||||
w "github.com/tobischo/gokeepasslib/v3/wrappers"
|
w "github.com/tobischo/gokeepasslib/v3/wrappers"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeepassExportCmd Exports a Passbolt Keepass
|
// KeepassExportCmd Exports a Passbolt Keepass
|
||||||
|
@ -58,12 +55,13 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
if keepassPassword == "" {
|
if keepassPassword == "" {
|
||||||
pw, err := util.ReadPassword("Enter Keepass Password:")
|
fmt.Print("Enter Keepass Password:")
|
||||||
|
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return fmt.Errorf("Reading Keepass Password: %w", err)
|
return fmt.Errorf("Reading Keepass Password: %w", err)
|
||||||
}
|
}
|
||||||
keepassPassword = pw
|
keepassPassword = string(bytepw)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,15 +91,22 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Progress: %w", err)
|
return fmt.Errorf("Progress: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, resource := range resources {
|
for i, resource := range resources {
|
||||||
entry, err := getKeepassEntry(client, resource, resource.Secrets[0], resource.ResourceType)
|
_, _, _, _, pass, desc, err := helper.GetResourceFromData(client, resource, resource.Secrets[0], resource.ResourceType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\nSkipping Export of Resource %v %v Because of: %v\n", resource.ID, resource.Name, err)
|
return fmt.Errorf("Get Resource %v, %v %w", i, resource.ID, err)
|
||||||
progressbar.Increment()
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rootGroup.Entries = append(rootGroup.Entries, *entry)
|
entry := gokeepasslib.NewEntry()
|
||||||
|
entry.Values = append(
|
||||||
|
entry.Values,
|
||||||
|
gokeepasslib.ValueData{Key: "Title", Value: gokeepasslib.V{Content: resource.Name}},
|
||||||
|
gokeepasslib.ValueData{Key: "UserName", Value: gokeepasslib.V{Content: resource.Username}},
|
||||||
|
gokeepasslib.ValueData{Key: "URL", Value: gokeepasslib.V{Content: resource.URI}},
|
||||||
|
gokeepasslib.ValueData{Key: "Password", Value: gokeepasslib.V{Content: pass, Protected: w.NewBoolWrapper(true)}},
|
||||||
|
gokeepasslib.ValueData{Key: "Notes", Value: gokeepasslib.V{Content: desc}},
|
||||||
|
)
|
||||||
|
rootGroup.Entries = append(rootGroup.Entries, entry)
|
||||||
progressbar.Increment()
|
progressbar.Increment()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,102 +133,3 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeepassEntry(client *api.Client, resource api.Resource, secret api.Secret, rType api.ResourceType) (*gokeepasslib.Entry, error) {
|
|
||||||
_, _, _, _, pass, desc, err := helper.GetResourceFromData(client, resource, resource.Secrets[0], resource.ResourceType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Get Resource %v: %w", resource.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := gokeepasslib.NewEntry()
|
|
||||||
entry.Values = append(
|
|
||||||
entry.Values,
|
|
||||||
gokeepasslib.ValueData{Key: "Title", Value: gokeepasslib.V{Content: resource.Name}},
|
|
||||||
gokeepasslib.ValueData{Key: "UserName", Value: gokeepasslib.V{Content: resource.Username}},
|
|
||||||
gokeepasslib.ValueData{Key: "URL", Value: gokeepasslib.V{Content: resource.URI}},
|
|
||||||
gokeepasslib.ValueData{Key: "Password", Value: gokeepasslib.V{Content: pass, Protected: w.NewBoolWrapper(true)}},
|
|
||||||
gokeepasslib.ValueData{Key: "Notes", Value: gokeepasslib.V{Content: desc}},
|
|
||||||
)
|
|
||||||
|
|
||||||
if resource.ResourceType.Slug == "password-description-totp" || resource.ResourceType.Slug == "totp" {
|
|
||||||
var totpData api.SecretDataTOTP
|
|
||||||
|
|
||||||
rawSecretData, err := client.DecryptMessage(resource.Secrets[0].Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Decrypting Secret Data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resource.ResourceType.Slug == "password-description-totp" {
|
|
||||||
var secretData api.SecretDataTypePasswordDescriptionTOTP
|
|
||||||
err = json.Unmarshal([]byte(rawSecretData), &secretData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Parsing Decrypted Secret Data: %w", err)
|
|
||||||
}
|
|
||||||
totpData = secretData.TOTP
|
|
||||||
} else {
|
|
||||||
var secretData api.SecretDataTypeTOTP
|
|
||||||
err = json.Unmarshal([]byte(rawSecretData), &secretData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Parsing Decrypted Secret Data: %w", err)
|
|
||||||
}
|
|
||||||
totpData = secretData.TOTP
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("secret", totpData.SecretKey)
|
|
||||||
v.Set("period", strconv.FormatUint(uint64(totpData.Period), 10))
|
|
||||||
v.Set("algorithm", totpData.Algorithm)
|
|
||||||
v.Set("digits", fmt.Sprint(totpData.Digits))
|
|
||||||
|
|
||||||
issuer := resource.URI
|
|
||||||
if resource.URI == "" {
|
|
||||||
issuer = resource.Name
|
|
||||||
|
|
||||||
}
|
|
||||||
v.Set("issuer", issuer)
|
|
||||||
|
|
||||||
accountName := resource.Username
|
|
||||||
if resource.Username == "" {
|
|
||||||
accountName = resource.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
u := url.URL{
|
|
||||||
Scheme: "otpauth",
|
|
||||||
Host: "totp",
|
|
||||||
Path: "/" + issuer + ":" + accountName,
|
|
||||||
RawQuery: encodeQuery(v),
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Values = append(entry.Values, gokeepasslib.ValueData{Key: "otp", Value: gokeepasslib.V{Content: u.String(), Protected: w.NewBoolWrapper(true)}})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &entry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeQuery is a copy-paste of url.Values.Encode, except it uses %20 instead
|
|
||||||
// of + to encode spaces. This is necessary to correctly render spaces in some
|
|
||||||
// authenticator apps, like Google Authenticator.
|
|
||||||
func encodeQuery(v url.Values) string {
|
|
||||||
if v == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var buf strings.Builder
|
|
||||||
keys := make([]string, 0, len(v))
|
|
||||||
for k := range v {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
vs := v[k]
|
|
||||||
keyEscaped := url.PathEscape(k) // changed from url.QueryEscape
|
|
||||||
for _, v := range vs {
|
|
||||||
if buf.Len() > 0 {
|
|
||||||
buf.WriteByte('&')
|
|
||||||
}
|
|
||||||
buf.WriteString(keyEscaped)
|
|
||||||
buf.WriteByte('=')
|
|
||||||
buf.WriteString(url.PathEscape(v)) // changed from url.QueryEscape
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
5
main.go
5
main.go
|
@ -1,10 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/passbolt/go-passbolt-cli/cmd"
|
||||||
"github.com/passbolt/go-passbolt-cli/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.SetVersionInfo(version, commit, date, dirty)
|
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
|
@ -55,10 +54,6 @@ func ResourceCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -83,18 +78,6 @@ func ResourceCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating Resource: %w", err)
|
return fmt.Errorf("Creating Resource: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
fmt.Printf("ResourceID: %v\n", id)
|
||||||
jsonId, err := json.MarshalIndent(
|
|
||||||
map[string]string{"id": id},
|
|
||||||
"",
|
|
||||||
" ",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Marshalling Json: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonId))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("ResourceID: %v\n", id)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
package resource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
|
||||||
"github.com/google/cel-go/common/types"
|
|
||||||
"github.com/google/cel-go/common/types/ref"
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
|
||||||
"github.com/passbolt/go-passbolt/api"
|
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Environments for CEl
|
|
||||||
var celEnvOptions = []cel.EnvOption{
|
|
||||||
cel.Variable("ID", cel.StringType),
|
|
||||||
cel.Variable("FolderParentID", cel.StringType),
|
|
||||||
cel.Variable("Name", cel.StringType),
|
|
||||||
cel.Variable("Username", cel.StringType),
|
|
||||||
cel.Variable("URI", cel.StringType),
|
|
||||||
cel.Variable("Password", cel.StringType),
|
|
||||||
cel.Variable("Description", cel.StringType),
|
|
||||||
cel.Variable("CreatedTimestamp", cel.TimestampType),
|
|
||||||
cel.Variable("ModifiedTimestamp", cel.TimestampType),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters the slice resources by invoke CEL program for each resource
|
|
||||||
func filterResources(resources *[]api.Resource, celCmd string, ctx context.Context, client *api.Client) ([]api.Resource, error) {
|
|
||||||
if celCmd == "" {
|
|
||||||
return *resources, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
program, err := util.InitCELProgram(celCmd, celEnvOptions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredResources := []api.Resource{}
|
|
||||||
for _, resource := range *resources {
|
|
||||||
val, _, err := (*program).ContextEval(ctx, map[string]any{
|
|
||||||
"Id": resource.ID,
|
|
||||||
"FolderParentID": resource.FolderParentID,
|
|
||||||
"Name": resource.Name,
|
|
||||||
"Username": resource.Username,
|
|
||||||
"URI": resource.URI,
|
|
||||||
"Password": func() ref.Val {
|
|
||||||
_, _, _, _, pass, _, err := helper.GetResource(ctx, client, resource.ID)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Get Resource %v", err)
|
|
||||||
return types.String("")
|
|
||||||
}
|
|
||||||
return types.String(pass)
|
|
||||||
},
|
|
||||||
"Description": func() ref.Val {
|
|
||||||
_, _, _, _, _, descr, err := helper.GetResource(ctx, client, resource.ID)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Get Resource %v", err)
|
|
||||||
return types.String("")
|
|
||||||
}
|
|
||||||
return types.String(descr)
|
|
||||||
},
|
|
||||||
"CreatedTimestamp": resource.Created.Time,
|
|
||||||
"ModifiedTimestamp": resource.Modified.Time,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.Value() == true {
|
|
||||||
filteredResources = append(filteredResources, resource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filteredResources) == 0 {
|
|
||||||
return nil, fmt.Errorf("No such Resources found with filter %v!", celCmd)
|
|
||||||
}
|
|
||||||
return filteredResources, nil
|
|
||||||
}
|
|
140
resource/get.go
140
resource/get.go
|
@ -2,16 +2,11 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/pterm/pterm"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,24 +18,10 @@ var ResourceGetCmd = &cobra.Command{
|
||||||
RunE: ResourceGet,
|
RunE: ResourceGet,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourcePermissionCmd Gets Permissions for Passbolt Resource
|
|
||||||
var ResourcePermissionCmd = &cobra.Command{
|
|
||||||
Use: "permission",
|
|
||||||
Short: "Gets Permissions for a Passbolt Resource",
|
|
||||||
Long: `Gets Permissions for a Passbolt Resource`,
|
|
||||||
Aliases: []string{"permissions"},
|
|
||||||
RunE: ResourcePermission,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceGetCmd.Flags().String("id", "", "id of Resource to Get")
|
ResourceGetCmd.Flags().String("id", "", "id of Resource to Get")
|
||||||
|
|
||||||
ResourceGetCmd.MarkFlagRequired("id")
|
ResourceGetCmd.MarkFlagRequired("id")
|
||||||
|
|
||||||
ResourceGetCmd.AddCommand(ResourcePermissionCmd)
|
|
||||||
ResourcePermissionCmd.Flags().String("id", "", "id of Resource to Get")
|
|
||||||
ResourcePermissionCmd.Flags().StringArrayP("column", "c", []string{"ID", "Aco", "AcoForeignKey", "Aro", "AroForeignKey", "Type"}, "Columns to return, possible Columns:\nID, Aco, AcoForeignKey, Aro, AroForeignKey, Type, CreatedTimestamp, ModifiedTimestamp")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResourceGet(cmd *cobra.Command, args []string) error {
|
func ResourceGet(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -48,10 +29,6 @@ func ResourceGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -70,114 +47,11 @@ func ResourceGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting Resource: %w", err)
|
return fmt.Errorf("Getting Resource: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("FolderParentID: %v\n", folderParentID)
|
||||||
if jsonOutput {
|
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
||||||
jsonResource, err := json.MarshalIndent(ResourceJsonOutput{
|
fmt.Printf("Username: %v\n", shellescape.StripUnsafe(username))
|
||||||
FolderParentID: &folderParentID,
|
fmt.Printf("URI: %v\n", shellescape.StripUnsafe(uri))
|
||||||
Name: &name,
|
fmt.Printf("Password: %v\n", shellescape.StripUnsafe(password))
|
||||||
Username: &username,
|
fmt.Printf("Description: %v\n", shellescape.StripUnsafe(description))
|
||||||
URI: &uri,
|
|
||||||
Password: &password,
|
|
||||||
Description: &description,
|
|
||||||
}, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonResource))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("FolderParentID: %v\n", folderParentID)
|
|
||||||
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
|
||||||
fmt.Printf("Username: %v\n", shellescape.StripUnsafe(username))
|
|
||||||
fmt.Printf("URI: %v\n", shellescape.StripUnsafe(uri))
|
|
||||||
fmt.Printf("Password: %v\n", shellescape.StripUnsafe(password))
|
|
||||||
fmt.Printf("Description: %v\n", shellescape.StripUnsafe(description))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResourcePermission(cmd *cobra.Command, args []string) error {
|
|
||||||
resource, err := cmd.Flags().GetString("id")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
columns, err := cmd.Flags().GetStringArray("column")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(columns) == 0 {
|
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
|
||||||
}
|
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
|
||||||
|
|
||||||
client, err := util.GetClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer client.Logout(context.TODO())
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
|
|
||||||
permissions, err := client.GetResourcePermissions(ctx, resource)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Listing Permission: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonOutput {
|
|
||||||
outputPermissions := []PermissionJsonOutput{}
|
|
||||||
for i := range permissions {
|
|
||||||
outputPermissions = append(outputPermissions, PermissionJsonOutput{
|
|
||||||
ID: &permissions[i].ID,
|
|
||||||
Aco: &permissions[i].ACO,
|
|
||||||
AcoForeignKey: &permissions[i].ACOForeignKey,
|
|
||||||
Aro: &permissions[i].ARO,
|
|
||||||
AroForeignKey: &permissions[i].AROForeignKey,
|
|
||||||
Type: &permissions[i].Type,
|
|
||||||
CreatedTimestamp: &permissions[i].Created.Time,
|
|
||||||
ModifiedTimestamp: &permissions[i].Modified.Time,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
jsonPermissions, err := json.MarshalIndent(outputPermissions, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonPermissions))
|
|
||||||
} else {
|
|
||||||
data := pterm.TableData{columns}
|
|
||||||
|
|
||||||
for _, permission := range permissions {
|
|
||||||
entry := make([]string, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
switch strings.ToLower(columns[i]) {
|
|
||||||
case "id":
|
|
||||||
entry[i] = permission.ID
|
|
||||||
case "aco":
|
|
||||||
entry[i] = permission.ACO
|
|
||||||
case "acoforeignkey":
|
|
||||||
entry[i] = permission.ACOForeignKey
|
|
||||||
case "aro":
|
|
||||||
entry[i] = permission.ARO
|
|
||||||
case "aroforeignkey":
|
|
||||||
entry[i] = permission.AROForeignKey
|
|
||||||
case "type":
|
|
||||||
entry[i] = strconv.Itoa(permission.Type)
|
|
||||||
case "createdtimestamp":
|
|
||||||
entry[i] = permission.Created.Format(time.RFC3339)
|
|
||||||
case "modifiedtimestamp":
|
|
||||||
entry[i] = permission.Modified.Format(time.RFC3339)
|
|
||||||
default:
|
|
||||||
cmd.SilenceUsage = false
|
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data = append(data, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package resource
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type ResourceJsonOutput struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
FolderParentID *string `json:"folder_parent_id,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
Username *string `json:"username,omitempty"`
|
|
||||||
URI *string `json:"uri,omitempty"`
|
|
||||||
Password *string `json:"password,omitempty"`
|
|
||||||
Description *string `json:"description,omitempty"`
|
|
||||||
CreatedTimestamp *time.Time `json:"created_timestamp,omitempty"`
|
|
||||||
ModifiedTimestamp *time.Time `json:"modified_timestamp,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PermissionJsonOutput struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
Aco *string `json:"aco,omitempty"`
|
|
||||||
AcoForeignKey *string `json:"aco_foreign_key,omitempty"`
|
|
||||||
Aro *string `json:"aro,omitempty"`
|
|
||||||
AroForeignKey *string `json:"aro_foreign_key,omitempty"`
|
|
||||||
Type *int `json:"type,omitempty"`
|
|
||||||
CreatedTimestamp *time.Time `json:"created_timestamp,omitempty"`
|
|
||||||
ModifiedTimestamp *time.Time `json:"modified_timestamp,omitempty"`
|
|
||||||
}
|
|
115
resource/list.go
115
resource/list.go
|
@ -2,12 +2,10 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
|
@ -28,9 +26,11 @@ var ResourceListCmd = &cobra.Command{
|
||||||
func init() {
|
func init() {
|
||||||
ResourceListCmd.Flags().Bool("favorite", false, "Resources that are marked as favorite")
|
ResourceListCmd.Flags().Bool("favorite", false, "Resources that are marked as favorite")
|
||||||
ResourceListCmd.Flags().Bool("own", false, "Resources that are owned by me")
|
ResourceListCmd.Flags().Bool("own", false, "Resources that are owned by me")
|
||||||
|
|
||||||
ResourceListCmd.Flags().StringP("group", "g", "", "Resources that are shared with group")
|
ResourceListCmd.Flags().StringP("group", "g", "", "Resources that are shared with group")
|
||||||
ResourceListCmd.Flags().StringArrayP("folder", "f", []string{}, "Resources that are in folder")
|
ResourceListCmd.Flags().StringArrayP("folder", "f", []string{}, "Resources that are in folder")
|
||||||
ResourceListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name", "Username", "URI"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, Username, URI, Password, Description, CreatedTimestamp, ModifiedTimestamp")
|
|
||||||
|
ResourceListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name", "Username", "URI"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, Username, URI, Password, Description")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResourceList(cmd *cobra.Command, args []string) error {
|
func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -57,14 +57,6 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
celFilter, err := cmd.Flags().GetString("filter")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -85,77 +77,42 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing Resource: %w", err)
|
return fmt.Errorf("Listing Resource: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resources, err = filterResources(&resources, celFilter, ctx, client)
|
data := pterm.TableData{columns}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonOutput {
|
for _, resource := range resources {
|
||||||
outputResources := []ResourceJsonOutput{}
|
entry := make([]string, len(columns))
|
||||||
for i := range resources {
|
for i := range columns {
|
||||||
_, _, _, _, pass, desc, err := helper.GetResource(ctx, client, resources[i].ID)
|
switch strings.ToLower(columns[i]) {
|
||||||
if err != nil {
|
case "id":
|
||||||
return fmt.Errorf("Get Resource %w", err)
|
entry[i] = resource.ID
|
||||||
}
|
case "folderparentid":
|
||||||
outputResources = append(outputResources, ResourceJsonOutput{
|
entry[i] = resource.FolderParentID
|
||||||
ID: &resources[i].ID,
|
case "name":
|
||||||
FolderParentID: &resources[i].FolderParentID,
|
entry[i] = shellescape.StripUnsafe(resource.Name)
|
||||||
Name: &resources[i].Name,
|
case "username":
|
||||||
Username: &resources[i].Username,
|
entry[i] = shellescape.StripUnsafe(resource.Username)
|
||||||
URI: &resources[i].URI,
|
case "uri":
|
||||||
Password: &pass,
|
entry[i] = shellescape.StripUnsafe(resource.URI)
|
||||||
Description: &desc,
|
case "password":
|
||||||
CreatedTimestamp: &resources[i].Created.Time,
|
_, _, _, _, pass, _, err := helper.GetResource(ctx, client, resource.ID)
|
||||||
ModifiedTimestamp: &resources[i].Modified.Time,
|
if err != nil {
|
||||||
})
|
return fmt.Errorf("Get Resource %w", err)
|
||||||
}
|
|
||||||
jsonResources, err := json.MarshalIndent(outputResources, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonResources))
|
|
||||||
} else {
|
|
||||||
data := pterm.TableData{columns}
|
|
||||||
|
|
||||||
for _, resource := range resources {
|
|
||||||
entry := make([]string, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
switch strings.ToLower(columns[i]) {
|
|
||||||
case "id":
|
|
||||||
entry[i] = resource.ID
|
|
||||||
case "folderparentid":
|
|
||||||
entry[i] = resource.FolderParentID
|
|
||||||
case "name":
|
|
||||||
entry[i] = shellescape.StripUnsafe(resource.Name)
|
|
||||||
case "username":
|
|
||||||
entry[i] = shellescape.StripUnsafe(resource.Username)
|
|
||||||
case "uri":
|
|
||||||
entry[i] = shellescape.StripUnsafe(resource.URI)
|
|
||||||
case "password":
|
|
||||||
_, _, _, _, pass, _, err := helper.GetResource(ctx, client, resource.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Get Resource %w", err)
|
|
||||||
}
|
|
||||||
entry[i] = shellescape.StripUnsafe(pass)
|
|
||||||
case "description":
|
|
||||||
_, _, _, _, _, desc, err := helper.GetResource(ctx, client, resource.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Get Resource %w", err)
|
|
||||||
}
|
|
||||||
entry[i] = shellescape.StripUnsafe(desc)
|
|
||||||
case "createdtimestamp":
|
|
||||||
entry[i] = resource.Created.Format(time.RFC3339)
|
|
||||||
case "modifiedtimestamp":
|
|
||||||
entry[i] = resource.Modified.Format(time.RFC3339)
|
|
||||||
default:
|
|
||||||
cmd.SilenceUsage = false
|
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
|
||||||
}
|
}
|
||||||
|
entry[i] = shellescape.StripUnsafe(pass)
|
||||||
|
case "description":
|
||||||
|
_, _, _, _, _, desc, err := helper.GetResource(ctx, client, resource.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Get Resource %w", err)
|
||||||
|
}
|
||||||
|
entry[i] = shellescape.StripUnsafe(desc)
|
||||||
|
default:
|
||||||
|
cmd.SilenceUsage = false
|
||||||
|
return fmt.Errorf("Unknown Column: %v", columns[i])
|
||||||
}
|
}
|
||||||
data = append(data, entry)
|
|
||||||
}
|
}
|
||||||
|
data = append(data, entry)
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
|
@ -46,11 +45,6 @@ func UserCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
client, err := util.GetClient(ctx)
|
client, err := util.GetClient(ctx)
|
||||||
|
@ -72,18 +66,6 @@ func UserCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating User: %w", err)
|
return fmt.Errorf("Creating User: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
fmt.Printf("UserID: %v\n", id)
|
||||||
jsonId, err := json.MarshalIndent(
|
|
||||||
map[string]string{"id": id},
|
|
||||||
"",
|
|
||||||
" ",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Marshalling Json: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonId))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("UserID: %v\n", id)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
|
||||||
"github.com/passbolt/go-passbolt/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Environments for CEl
|
|
||||||
var celEnvOptions = []cel.EnvOption{
|
|
||||||
cel.Variable("ID", cel.StringType),
|
|
||||||
cel.Variable("Username", cel.StringType),
|
|
||||||
cel.Variable("FirstName", cel.StringType),
|
|
||||||
cel.Variable("LastName", cel.StringType),
|
|
||||||
cel.Variable("Role", cel.StringType),
|
|
||||||
cel.Variable("CreatedTimestamp", cel.TimestampType),
|
|
||||||
cel.Variable("ModifiedTimestamp", cel.TimestampType),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters the slice users by invoke CEL program for each user
|
|
||||||
func filterUsers(users *[]api.User, celCmd string, ctx context.Context) ([]api.User, error) {
|
|
||||||
if celCmd == "" {
|
|
||||||
return *users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
program, err := util.InitCELProgram(celCmd, celEnvOptions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredUsers := []api.User{}
|
|
||||||
for _, user := range *users {
|
|
||||||
val, _, err := (*program).ContextEval(ctx, map[string]any{
|
|
||||||
"ID": user.ID,
|
|
||||||
"Username": user.Username,
|
|
||||||
"FirstName": user.Profile.FirstName,
|
|
||||||
"LastName": user.Profile.LastName,
|
|
||||||
"Role": user.Role.Name,
|
|
||||||
"CreatedTimestamp": user.Created.Time,
|
|
||||||
"ModifiedTimestamp": user.Modified.Time,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.Value() == true {
|
|
||||||
filteredUsers = append(filteredUsers, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filteredUsers) == 0 {
|
|
||||||
return nil, fmt.Errorf("No such users found with filter %v!", celCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredUsers, nil
|
|
||||||
}
|
|
29
user/get.go
29
user/get.go
|
@ -2,10 +2,9 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -30,10 +29,6 @@ func UserGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -52,22 +47,10 @@ func UserGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting User: %w", err)
|
return fmt.Errorf("Getting User: %w", err)
|
||||||
}
|
}
|
||||||
if jsonOutput {
|
fmt.Printf("Username: %v\n", shellescape.StripUnsafe(username))
|
||||||
jsonUser, err := json.MarshalIndent(UserJsonOutput{
|
fmt.Printf("FirstName: %v\n", shellescape.StripUnsafe(firstname))
|
||||||
Username: &username,
|
fmt.Printf("LastName: %v\n", shellescape.StripUnsafe(lastname))
|
||||||
FirstName: &firstname,
|
fmt.Printf("Role: %v\n", shellescape.StripUnsafe(role))
|
||||||
LastName: &lastname,
|
|
||||||
Role: &role,
|
|
||||||
}, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(jsonUser))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Username: %v\n", shellescape.StripUnsafe(username))
|
|
||||||
fmt.Printf("FirstName: %v\n", shellescape.StripUnsafe(firstname))
|
|
||||||
fmt.Printf("LastName: %v\n", shellescape.StripUnsafe(lastname))
|
|
||||||
fmt.Printf("Role: %v\n", shellescape.StripUnsafe(role))
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
13
user/json.go
13
user/json.go
|
@ -1,13 +0,0 @@
|
||||||
package user
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type UserJsonOutput struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
Username *string `json:"username,omitempty"`
|
|
||||||
FirstName *string `json:"first_name,omitempty"`
|
|
||||||
LastName *string `json:"last_name,omitempty"`
|
|
||||||
Role *string `json:"role,omitempty"`
|
|
||||||
CreatedTimestamp *time.Time `json:"created_timestamp,omitempty"`
|
|
||||||
ModifiedTimestamp *time.Time `json:"modified_timestamp,omitempty"`
|
|
||||||
}
|
|
85
user/list.go
85
user/list.go
|
@ -2,12 +2,10 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"al.essio.dev/pkg/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -31,7 +29,7 @@ func init() {
|
||||||
UserListCmd.Flags().StringP("search", "s", "", "Search for Users")
|
UserListCmd.Flags().StringP("search", "s", "", "Search for Users")
|
||||||
UserListCmd.Flags().BoolP("admin", "a", false, "Only show Admins")
|
UserListCmd.Flags().BoolP("admin", "a", false, "Only show Admins")
|
||||||
|
|
||||||
UserListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Username", "FirstName", "LastName", "Role"}, "Columns to return, possible Columns:\nID, Username, FirstName, LastName, Role, CreatedTimestamp, ModifiedTimestamp")
|
UserListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Username", "FirstName", "LastName", "Role"}, "Columns to return, possible Columns:\nID, Username, FirstName, LastName, Role")
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserList(cmd *cobra.Command, args []string) error {
|
func UserList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -58,14 +56,6 @@ func UserList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
}
|
}
|
||||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
celFilter, err := cmd.Flags().GetString("filter")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -86,59 +76,30 @@ func UserList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing User: %w", err)
|
return fmt.Errorf("Listing User: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err = filterUsers(&users, celFilter, ctx)
|
data := pterm.TableData{columns}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonOutput {
|
for _, user := range users {
|
||||||
outputUsers := []UserJsonOutput{}
|
entry := make([]string, len(columns))
|
||||||
for i := range users {
|
for i := range columns {
|
||||||
outputUsers = append(outputUsers, UserJsonOutput{
|
switch strings.ToLower(columns[i]) {
|
||||||
ID: &users[i].ID,
|
case "id":
|
||||||
Username: &users[i].Username,
|
entry[i] = user.ID
|
||||||
FirstName: &users[i].Profile.FirstName,
|
case "username":
|
||||||
LastName: &users[i].Profile.LastName,
|
entry[i] = shellescape.StripUnsafe(user.Username)
|
||||||
Role: &users[i].Role.Name,
|
case "firstname":
|
||||||
CreatedTimestamp: &users[i].Created.Time,
|
entry[i] = shellescape.StripUnsafe(user.Profile.FirstName)
|
||||||
ModifiedTimestamp: &users[i].Modified.Time,
|
case "lastname":
|
||||||
})
|
entry[i] = shellescape.StripUnsafe(user.Profile.LastName)
|
||||||
}
|
case "role":
|
||||||
jsonUsers, err := json.MarshalIndent(outputUsers, "", " ")
|
entry[i] = shellescape.StripUnsafe(user.Role.Name)
|
||||||
if err != nil {
|
default:
|
||||||
return err
|
cmd.SilenceUsage = false
|
||||||
}
|
return fmt.Errorf("Unknown Column: %v", columns[i])
|
||||||
fmt.Println(string(jsonUsers))
|
|
||||||
} else {
|
|
||||||
data := pterm.TableData{columns}
|
|
||||||
|
|
||||||
for _, user := range users {
|
|
||||||
entry := make([]string, len(columns))
|
|
||||||
for i := range columns {
|
|
||||||
switch strings.ToLower(columns[i]) {
|
|
||||||
case "id":
|
|
||||||
entry[i] = user.ID
|
|
||||||
case "username":
|
|
||||||
entry[i] = shellescape.StripUnsafe(user.Username)
|
|
||||||
case "firstname":
|
|
||||||
entry[i] = shellescape.StripUnsafe(user.Profile.FirstName)
|
|
||||||
case "lastname":
|
|
||||||
entry[i] = shellescape.StripUnsafe(user.Profile.LastName)
|
|
||||||
case "role":
|
|
||||||
entry[i] = shellescape.StripUnsafe(user.Role.Name)
|
|
||||||
case "createdtimestamp":
|
|
||||||
entry[i] = user.Created.Format(time.RFC3339)
|
|
||||||
case "modifiedtimestamp":
|
|
||||||
entry[i] = user.Modified.Format(time.RFC3339)
|
|
||||||
default:
|
|
||||||
cmd.SilenceUsage = false
|
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data = append(data, entry)
|
|
||||||
}
|
}
|
||||||
|
data = append(data, entry)
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
23
util/cel.go
23
util/cel.go
|
@ -1,23 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import "github.com/google/cel-go/cel"
|
|
||||||
|
|
||||||
// InitCELProgram - Initialize a CEL program with given CEL command and a set of environments
|
|
||||||
func InitCELProgram(celCmd string, options ...cel.EnvOption) (*cel.Program, error) {
|
|
||||||
env, err := cel.NewEnv(options...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ast, issue := env.Compile(celCmd)
|
|
||||||
if issue.Err() != nil {
|
|
||||||
return nil, issue.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
program, err := env.Program(ast)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &program, nil
|
|
||||||
}
|
|
|
@ -1,15 +1,12 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"syscall"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
|
@ -17,30 +14,6 @@ import (
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadPassword reads a Password interactively or via Pipe
|
|
||||||
func ReadPassword(prompt string) (string, error) {
|
|
||||||
fd := int(os.Stdin.Fd())
|
|
||||||
var pass string
|
|
||||||
if term.IsTerminal(fd) {
|
|
||||||
fmt.Fprint(os.Stderr, prompt);
|
|
||||||
|
|
||||||
inputPass, err := term.ReadPassword(fd)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
pass = string(inputPass)
|
|
||||||
} else {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
s, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
pass = s
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Replace(pass, "\n", "", 1), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClient gets a Logged in Passbolt Client
|
// GetClient gets a Logged in Passbolt Client
|
||||||
func GetClient(ctx context.Context) (*api.Client, error) {
|
func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
serverAddress := viper.GetString("serverAddress")
|
serverAddress := viper.GetString("serverAddress")
|
||||||
|
@ -55,21 +28,17 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
|
|
||||||
userPassword := viper.GetString("userPassword")
|
userPassword := viper.GetString("userPassword")
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
cliPassword, err := ReadPassword("Enter Password:")
|
fmt.Print("Enter Password:")
|
||||||
|
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return nil, fmt.Errorf("Reading Password: %w", err)
|
return nil, fmt.Errorf("Reading Password: %w", err)
|
||||||
}
|
}
|
||||||
|
userPassword = string(bytepw)
|
||||||
userPassword = cliPassword
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient, err := GetHttpClient()
|
client, err := api.NewClient(nil, "", serverAddress, userPrivateKey, userPassword)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Creating Client: %w", err)
|
return nil, fmt.Errorf("Creating Client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -89,30 +58,32 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
switch viper.GetString("mfaMode") {
|
switch viper.GetString("mfaMode") {
|
||||||
case "interactive-totp":
|
case "interactive-totp":
|
||||||
client.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
|
client.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
|
||||||
challenge := api.MFAChallenge{}
|
challange := api.MFAChallange{}
|
||||||
err := json.Unmarshal(res.Body, &challenge)
|
err := json.Unmarshal(res.Body, &challange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.Cookie{}, fmt.Errorf("Parsing MFA Challenge")
|
return http.Cookie{}, fmt.Errorf("Parsing MFA Challange")
|
||||||
}
|
}
|
||||||
if challenge.Provider.TOTP == "" {
|
if challange.Provider.TOTP == "" {
|
||||||
return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider")
|
return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider")
|
||||||
}
|
}
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
var code string
|
var code string
|
||||||
code, err := ReadPassword("Enter TOTP:")
|
fmt.Print("Enter TOTP:")
|
||||||
|
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
|
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
|
||||||
}
|
}
|
||||||
|
code = string(bytepw)
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
req := api.MFAChallengeResponse{
|
req := api.MFAChallangeResponse{
|
||||||
TOTP: code,
|
TOTP: code,
|
||||||
}
|
}
|
||||||
var raw *http.Response
|
var raw *http.Response
|
||||||
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
|
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
|
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
|
||||||
return http.Cookie{}, fmt.Errorf("Doing MFA Challenge Response: %w", err)
|
return http.Cookie{}, fmt.Errorf("Doing MFA Challange Response: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("TOTP Verification Failed")
|
fmt.Println("TOTP Verification Failed")
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,21 +96,10 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
return http.Cookie{}, fmt.Errorf("Unable to find Passbolt MFA Cookie")
|
return http.Cookie{}, fmt.Errorf("Unable to find Passbolt MFA Cookie")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return http.Cookie{}, fmt.Errorf("Failed MFA Challenge 3 times: %w", err)
|
return http.Cookie{}, fmt.Errorf("Failed MFA Challange 3 times: %w", err)
|
||||||
}
|
}
|
||||||
case "noninteractive-totp":
|
case "noninteractive-totp":
|
||||||
// if new flag is unset, use old flag instead
|
helper.AddMFACallbackTOTP(client, viper.GetUint("mfaRetrys"), viper.GetDuration("mfaDelay"), viper.GetDuration("totpOffset"), viper.GetString("totpToken"))
|
||||||
totpToken := viper.GetString("mfaTotpToken")
|
|
||||||
if totpToken == "" {
|
|
||||||
totpToken = viper.GetString("totpToken")
|
|
||||||
}
|
|
||||||
|
|
||||||
totpOffset := viper.GetDuration("mfaTotpOffset")
|
|
||||||
if totpOffset == time.Duration(0) {
|
|
||||||
totpOffset = viper.GetDuration("totpOffset")
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.AddMFACallbackTOTP(client, viper.GetUint("mfaRetrys"), viper.GetDuration("mfaDelay"), totpOffset, totpToken)
|
|
||||||
case "none":
|
case "none":
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
44
util/http.go
44
util/http.go
|
@ -1,44 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetClientCertificate() (tls.Certificate, error) {
|
|
||||||
cert := viper.GetString("tlsClientCert")
|
|
||||||
certExists := cert != ""
|
|
||||||
key := viper.GetString("tlsClientPrivateKey")
|
|
||||||
keyExists := key != ""
|
|
||||||
if !certExists && !keyExists {
|
|
||||||
return tls.Certificate{}, nil
|
|
||||||
}
|
|
||||||
if certExists && !keyExists {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("Client TLS private key is empty, but client TLS cert was set.")
|
|
||||||
}
|
|
||||||
if !certExists && keyExists {
|
|
||||||
return tls.Certificate{}, fmt.Errorf("Client TLS cert is empty, but client TLS private key was set.")
|
|
||||||
}
|
|
||||||
return tls.X509KeyPair([]byte(cert), []byte(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHttpClient() (*http.Client, error) {
|
|
||||||
tlsSkipVerify := viper.GetBool("tlsSkipVerify")
|
|
||||||
cert, err := GetClientCertificate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
httpClient := http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
InsecureSkipVerify: tlsSkipVerify,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &httpClient, nil
|
|
||||||
}
|
|
41
version.go
41
version.go
|
@ -1,41 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
version = "unknown"
|
|
||||||
commit = "unknown"
|
|
||||||
date = "unknown"
|
|
||||||
dirty = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// if not set by goreleaser, use buildinfo instead
|
|
||||||
if version == "unknown" {
|
|
||||||
info, ok := debug.ReadBuildInfo()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if info.Main.Version != "" {
|
|
||||||
version = info.Main.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, kv := range info.Settings {
|
|
||||||
if kv.Value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch kv.Key {
|
|
||||||
case "vcs.revision":
|
|
||||||
commit = kv.Value
|
|
||||||
case "vcs.time":
|
|
||||||
d, _ := time.Parse(time.RFC3339, kv.Value)
|
|
||||||
date = d.String()
|
|
||||||
case "vcs.modified":
|
|
||||||
dirty = kv.Value == "true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue