mirror of
https://github.com/passbolt/go-passbolt-cli.git
synced 2025-05-12 10:58:21 +00:00
Compare commits
105 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
43cc96a43d | ||
b1a82e06d6 | |||
![]() |
1183813dcb | ||
6033d6bbb3 | |||
![]() |
78ed21f62b | ||
![]() |
0273cee2ba | ||
![]() |
72cfd79b77 | ||
![]() |
d5e2df49db | ||
d9703ff6fd | |||
![]() |
9b2d47fecd | ||
c0dd780ca3 | |||
1fcbafa5b1 | |||
9a9d06987a | |||
647a063586 | |||
c454e733cf | |||
![]() |
a74c5e0550 | ||
b11702da24 | |||
1e09efcc5d | |||
6cb52e23ca | |||
b48f3274e6 | |||
b269e8538d | |||
13da79adf6 | |||
effdbdb316 | |||
3cbabb905a | |||
eeaa987bb6 | |||
c5e1dcbedc | |||
774fc8b54b | |||
4a870ea137 | |||
4975d53b4d | |||
b3e608e6c6 | |||
e0469a14b0 | |||
1229ad7b59 | |||
869f33cfa3 | |||
6f4d6e92b7 | |||
7da6aec339 | |||
c595eca741 | |||
a535a832c2 | |||
fafdd22d73 | |||
54d424390b | |||
52bdc35875 | |||
c2506ed71a | |||
2e1a46f5ce | |||
444fa17005 | |||
abb8895df3 | |||
4a15fe34ad | |||
c006252c66 | |||
2fd123692b | |||
61df03f10a | |||
a9ce43584a | |||
92a05e899e | |||
d4c5ab93b8 | |||
7883419856 | |||
fb1e4e97fe | |||
12cb8bb643 | |||
22c8648884 | |||
82542147f2 | |||
3b9406f0f0 | |||
acd18bb0f7 | |||
1cd6ab33ac | |||
488bf7043a | |||
![]() |
ad51d8134f | ||
![]() |
6169450ab5 | ||
![]() |
7fe89a34b3 | ||
![]() |
0a542bfdba | ||
![]() |
f30590588e | ||
![]() |
6562aeab95 | ||
![]() |
20e3a72c92 | ||
![]() |
7db321f130 | ||
![]() |
5ee20771a8 | ||
![]() |
4e5983dc43 | ||
![]() |
e5bb6eceff | ||
![]() |
9bdc4a96ca | ||
![]() |
3d395a11ed | ||
![]() |
48604aefd0 | ||
![]() |
5a464f48da | ||
![]() |
a0b7b7daaf | ||
![]() |
e9582729ad | ||
![]() |
0aa392634b | ||
![]() |
34fedec4f2 | ||
![]() |
6d4879e8b4 | ||
![]() |
6f59446349 | ||
![]() |
9a2944c6fb | ||
![]() |
05dd936fbc | ||
![]() |
4dcb528f70 | ||
![]() |
269e117fbf | ||
![]() |
e8bb791686 | ||
![]() |
c8dc067697 | ||
![]() |
a2257bc011 | ||
![]() |
46ceb8176d | ||
![]() |
45fe21119b | ||
![]() |
d142f14ccc | ||
![]() |
423dfb020c | ||
![]() |
cbf2771935 | ||
![]() |
94165a0f3d | ||
![]() |
2b52c420c4 | ||
![]() |
0804a1d9b1 | ||
![]() |
3ee8f88bbb | ||
![]() |
89abb4f5a3 | ||
![]() |
6d5327cd95 | ||
![]() |
fe7e6b2a22 | ||
![]() |
e5c267da42 | ||
![]() |
440f41a2af | ||
![]() |
bfbc15bd0d | ||
![]() |
a8b14c959f | ||
![]() |
df187e8a5f |
38 changed files with 1704 additions and 1100 deletions
2
.github/workflows/.docs.yml
vendored
2
.github/workflows/.docs.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.21
|
||||
|
||||
- name: Build
|
||||
run: go build -o passbolt
|
||||
|
|
33
.github/workflows/.release.yml
vendored
33
.github/workflows/.release.yml
vendored
|
@ -13,32 +13,45 @@ jobs:
|
|||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.21
|
||||
-
|
||||
name: Generate Man and Completions
|
||||
run: |
|
||||
mkdir completion
|
||||
go run main.go completion bash > completion/bash
|
||||
go run main.go completion zsh > completion/zsh
|
||||
go run main.go completion fish > completion/fish
|
||||
go run main.go completion powershell > completion/powershell
|
||||
go run *.go completion bash > completion/bash
|
||||
go run *.go completion zsh > completion/zsh
|
||||
go run *.go completion fish > completion/fish
|
||||
go run *.go completion powershell > completion/powershell
|
||||
mkdir man
|
||||
go run main.go gendoc --type man
|
||||
go run *.go gendoc --type man
|
||||
pwd
|
||||
ls
|
||||
-
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
args: release --clean ${{ env.flags }}
|
||||
env:
|
||||
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,13 +14,7 @@ builds:
|
|||
- arm64
|
||||
binary: passbolt
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
files:
|
||||
- files:
|
||||
- completion/*
|
||||
- man/*
|
||||
format_overrides:
|
||||
|
@ -31,7 +25,7 @@ release:
|
|||
header: |
|
||||
## Release {{ .Tag }} - ({{ .Date }})
|
||||
nfpms:
|
||||
- maintainer: Samuel Lorch <sam@lorch.net>
|
||||
- maintainer: Samuel Lorch <sam@soontm.de>
|
||||
description: A CLI for Passbolt.
|
||||
homepage: https://github.com/passbolt/go-passbolt-cli
|
||||
license: MIT
|
||||
|
@ -49,3 +43,22 @@ nfpms:
|
|||
formats:
|
||||
- deb
|
||||
- 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,22 +1,30 @@
|
|||
# go-passbolt-cli
|
||||
A CLI tool to interact with Passbolt, a Open source Password Manager for Teams.
|
||||
A CLI tool to interact with Passbolt, an Open source Password Manager for teams.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
Disclaimer: This project is community driven and not associated with Passbolt SA
|
||||
# Install
|
||||
|
||||
## Via Package (Prefered):
|
||||
Download the Package for your OS and architecture from the Latest Release.
|
||||
## Via Repository (Prefered):
|
||||
[](https://repology.org/project/go:passbolt-cli/versions)
|
||||
|
||||
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`
|
||||
|
||||
## Via Homebrew
|
||||
brew install passbolt/tap/go-passbolt-cli
|
||||
|
||||
## Via Archive:
|
||||
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.
|
||||
|
||||
## Via Go:
|
||||
go install github.com/passbolt/go-passbolt-cli
|
||||
go install github.com/passbolt/go-passbolt-cli@latest
|
||||
Note: this will install the binary as go-passbolt-cli, also tab completion and manpages will be missing.
|
||||
|
||||
# Getting Started
|
||||
|
@ -30,35 +38,35 @@ or
|
|||
```
|
||||
passbolt configure --serverAddress https://passbolt.example.org --userPassword '1234' --userPrivateKey '-----BEGIN PGP PRIVATE KEY BLOCK-----'
|
||||
```
|
||||
- Setup Enviroment Variables
|
||||
- Setup Environment Variables
|
||||
- Provide the Flags manually every time
|
||||
|
||||
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 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
|
||||
- MFA settings can also be save permenantly this ways
|
||||
- MFA settings can also be save permanently this ways
|
||||
|
||||
# Usage
|
||||
|
||||
Generally the Structure of Commands is like this:
|
||||
```bash
|
||||
go-passbolt-cli action entity [arguments]
|
||||
passbolt action entity [arguments]
|
||||
```
|
||||
|
||||
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 a action to.
|
||||
Action is the Action you want to perform like Creating, Updating or Deleting an Entity.
|
||||
Entity is a Resource(Password), Folder, User or Group that you want to apply an action to.
|
||||
|
||||
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:
|
||||
```bash
|
||||
go-passbolt-cli create resource --name "Test Resource" --password "Strong Password"
|
||||
passbolt create resource --name "Test Resource" --password "Strong Password"
|
||||
```
|
||||
|
||||
You can then list all users:
|
||||
```bash
|
||||
go-passbolt-cli list user
|
||||
passbolt 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.
|
||||
|
||||
|
@ -74,25 +82,43 @@ 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:
|
||||
```bash
|
||||
go-passbolt-cli share resource --id id_of_resource_to_share --type type_of_permission --user id_of_user_to_share_with
|
||||
passbolt 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
|
||||
|
||||
For sharing with groups the `--group` argument exists.
|
||||
|
||||
# MFA
|
||||
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`.
|
||||
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`.
|
||||
| Mode | Description |
|
||||
| --- | --- |
|
||||
|`none`|just errors if challanged for MFA.
|
||||
|`none`|just errors if challenged for MFA.
|
||||
|`interactive-totp` | prompts for interactive entry of TOTP Codes.
|
||||
|`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
|
||||
|`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
|
||||
|
||||
|
||||
# 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
|
||||
Usage for all Subcommands is [here](https://github.com/passbolt/go-passbolt-cli/wiki/go-passbolt-cli).
|
||||
Usage for all Subcommands is [here](https://github.com/passbolt/go-passbolt-cli/wiki/passbolt).
|
||||
And is also available via `man passbolt`
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ var createCmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
rootCmd.AddCommand(createCmd)
|
||||
createCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
||||
createCmd.AddCommand(resource.ResourceCreateCmd)
|
||||
createCmd.AddCommand(folder.FolderCreateCmd)
|
||||
createCmd.AddCommand(group.GroupCreateCmd)
|
||||
|
|
103
cmd/exec.go
Normal file
103
cmd/exec.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
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,6 +18,7 @@ var getCmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
rootCmd.AddCommand(getCmd)
|
||||
getCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
||||
getCmd.AddCommand(resource.ResourceGetCmd)
|
||||
getCmd.AddCommand(folder.FolderGetCmd)
|
||||
getCmd.AddCommand(group.GroupGetCmd)
|
||||
|
|
|
@ -18,6 +18,13 @@ var listCmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
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(folder.FolderListCmd)
|
||||
listCmd.AddCommand(group.GroupListCmd)
|
||||
|
|
66
cmd/root.go
66
cmd/root.go
|
@ -2,7 +2,6 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
@ -48,11 +47,24 @@ 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("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("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().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().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("timeout", rootCmd.PersistentFlags().Lookup("timeout"))
|
||||
viper.BindPFlag("serverAddress", rootCmd.PersistentFlags().Lookup("serverAddress"))
|
||||
|
@ -60,9 +72,27 @@ func init() {
|
|||
viper.BindPFlag("userPassword", rootCmd.PersistentFlags().Lookup("userPassword"))
|
||||
viper.BindPFlag("mfaMode", rootCmd.PersistentFlags().Lookup("mfaMode"))
|
||||
viper.BindPFlag("totpToken", rootCmd.PersistentFlags().Lookup("totpToken"))
|
||||
viper.BindPFlag("mfaTotpToken", rootCmd.PersistentFlags().Lookup("mfaTotpToken"))
|
||||
viper.BindPFlag("totpOffset", rootCmd.PersistentFlags().Lookup("totpOffset"))
|
||||
viper.BindPFlag("mfaTotpOffset", rootCmd.PersistentFlags().Lookup("mfaTotpOffset"))
|
||||
viper.BindPFlag("mfaRetrys", rootCmd.PersistentFlags().Lookup("mfaRetrys"))
|
||||
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.
|
||||
|
@ -98,16 +128,32 @@ func initConfig() {
|
|||
// Read in Private Key from File if userprivatekeyfile is set
|
||||
userprivatekeyfile, err := rootCmd.PersistentFlags().GetString("userPrivateKeyFile")
|
||||
if err == nil && userprivatekeyfile != "" {
|
||||
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))
|
||||
fileToContent(userprivatekeyfile, "userPrivateKey")
|
||||
} else if err != nil && viper.GetBool("debug") {
|
||||
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,13 +2,11 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// verifyCMD represents the verify command
|
||||
|
@ -34,17 +32,20 @@ var verifyCMD = &cobra.Command{
|
|||
|
||||
userPassword := viper.GetString("userPassword")
|
||||
if userPassword == "" {
|
||||
fmt.Print("Enter Password:")
|
||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||
pw, err := util.ReadPassword("Enter Password:")
|
||||
if err != nil {
|
||||
fmt.Println()
|
||||
return fmt.Errorf("Reading Password: %w", err)
|
||||
}
|
||||
userPassword = string(bytepw)
|
||||
userPassword = pw
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
client, err := api.NewClient(nil, "", serverAddress, userPrivateKey, userPassword)
|
||||
httpClient, err := util.GetHttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Creating Client: %w", err)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package folder
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
|
@ -33,6 +34,10 @@ func FolderCreate(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -53,6 +58,18 @@ func FolderCreate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Creating Folder: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
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
|
||||
}
|
||||
|
|
56
folder/filter.go
Normal file
56
folder/filter.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/helper"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -29,6 +29,10 @@ func FolderGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -39,15 +43,22 @@ func FolderGet(cmd *cobra.Command, args []string) error {
|
|||
defer client.Logout(context.TODO())
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
folderParentID, name, err := helper.GetFolder(
|
||||
ctx,
|
||||
client,
|
||||
id,
|
||||
)
|
||||
folder, err := client.GetFolder(ctx, id, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Getting Folder: %w", err)
|
||||
}
|
||||
fmt.Printf("FolderParentID: %v\n", folderParentID)
|
||||
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
||||
if jsonOutput {
|
||||
jsonGroup, err := json.MarshalIndent(FolderJsonOutput{
|
||||
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
|
||||
}
|
||||
|
|
11
folder/json.go
Normal file
11
folder/json.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
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,10 +2,12 @@ package folder
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -26,7 +28,7 @@ func init() {
|
|||
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("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")
|
||||
FolderListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, CreatedTimestamp, ModifiedTimestamp")
|
||||
}
|
||||
|
||||
func FolderList(cmd *cobra.Command, args []string) error {
|
||||
|
@ -45,6 +47,14 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
|||
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
|
||||
}
|
||||
celFilter, err := cmd.Flags().GetString("filter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
cmd.SilenceUsage = true
|
||||
|
@ -63,6 +73,28 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Listing Folder: %w", err)
|
||||
}
|
||||
|
||||
folders, err = filterFolders(&folders, celFilter, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputFolders := []FolderJsonOutput{}
|
||||
for i := range folders {
|
||||
outputFolders = append(outputFolders, FolderJsonOutput{
|
||||
ID: &folders[i].ID,
|
||||
FolderParentID: &folders[i].FolderParentID,
|
||||
Name: &folders[i].Name,
|
||||
CreatedTimestamp: &folders[i].Created.Time,
|
||||
ModifiedTimestamp: &folders[i].Modified.Time,
|
||||
})
|
||||
}
|
||||
jsonFolders, err := json.MarshalIndent(outputFolders, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonFolders))
|
||||
} else {
|
||||
data := pterm.TableData{columns}
|
||||
|
||||
for _, folder := range folders {
|
||||
|
@ -75,6 +107,10 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
|||
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])
|
||||
|
@ -84,5 +120,6 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
76
go.mod
76
go.mod
|
@ -1,23 +1,67 @@
|
|||
module github.com/passbolt/go-passbolt-cli
|
||||
|
||||
go 1.16
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.6
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.6 // indirect
|
||||
github.com/alessio/shellescape v1.4.1
|
||||
github.com/gookit/color v1.5.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/passbolt/go-passbolt v0.5.7
|
||||
github.com/pterm/pterm v0.12.41
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/tobischo/gokeepasslib/v3 v3.2.4
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
al.essio.dev/pkg/shellescape v1.5.1
|
||||
github.com/google/cel-go v0.24.1
|
||||
github.com/passbolt/go-passbolt v0.7.2
|
||||
github.com/pterm/pterm v0.12.80
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/tobischo/gokeepasslib/v3 v3.6.1
|
||||
golang.org/x/term v0.29.0
|
||||
)
|
||||
|
||||
require (
|
||||
atomicgo.dev/cursor v0.2.0 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/schedule v0.1.0 // indirect
|
||||
cel.dev/expr v0.21.2 // 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
|
||||
|
|
|
@ -2,6 +2,7 @@ package group
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
|
@ -40,6 +41,10 @@ func GroupCreate(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ops := []helper.GroupMembershipOperation{}
|
||||
for _, user := range users {
|
||||
|
@ -74,6 +79,18 @@ func GroupCreate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Creating Group: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
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
|
||||
}
|
||||
|
|
54
group/filter.go
Normal file
54
group/filter.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
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
|
||||
}
|
31
group/get.go
31
group/get.go
|
@ -2,10 +2,11 @@ package group
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/helper"
|
||||
"github.com/pterm/pterm"
|
||||
|
@ -37,6 +38,10 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -55,6 +60,29 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("Getting Group: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
groupUserMemberships := []GroupUserMembershipJsonOutput{}
|
||||
for i := range memberships {
|
||||
groupUserMemberships = append(groupUserMemberships, GroupUserMembershipJsonOutput{
|
||||
ID: &memberships[i].UserID,
|
||||
Username: &memberships[i].Username,
|
||||
FirstName: &memberships[i].UserFirstName,
|
||||
LastName: &memberships[i].UserLastName,
|
||||
IsGroupManager: &memberships[i].IsGroupManager,
|
||||
})
|
||||
}
|
||||
|
||||
jsonGroup, err := json.MarshalIndent(GroupJsonOutput{
|
||||
Name: &name,
|
||||
Users: groupUserMemberships,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonGroup))
|
||||
|
||||
} else {
|
||||
fmt.Printf("Name: %v\n", name)
|
||||
// Print Memberships
|
||||
if len(columns) != 0 {
|
||||
|
@ -84,5 +112,6 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
|||
|
||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
19
group/json.go
Normal file
19
group/json.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
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,10 +2,12 @@ package group
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -26,7 +28,7 @@ func init() {
|
|||
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("column", "c", []string{"ID", "Name"}, "Columns to return, possible Columns:\nID, Name")
|
||||
GroupListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Name"}, "Columns to return, possible Columns:\nID, Name, CreatedTimestamp, ModifiedTimestamp")
|
||||
}
|
||||
|
||||
func GroupList(cmd *cobra.Command, args []string) error {
|
||||
|
@ -45,6 +47,14 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
|||
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
|
||||
}
|
||||
celFilter, err := cmd.Flags().GetString("filter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -55,7 +65,7 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
|||
defer client.Logout(context.TODO())
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
resources, err := client.GetGroups(ctx, &api.GetGroupsOptions{
|
||||
groups, err := client.GetGroups(ctx, &api.GetGroupsOptions{
|
||||
FilterHasUsers: users,
|
||||
FilterHasManagers: managers,
|
||||
})
|
||||
|
@ -63,16 +73,41 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Listing Group: %w", err)
|
||||
}
|
||||
|
||||
groups, err = filterGroups(&groups, celFilter, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputGroups := []GroupJsonOutput{}
|
||||
for i := range groups {
|
||||
outputGroups = append(outputGroups, GroupJsonOutput{
|
||||
ID: &groups[i].ID,
|
||||
Name: &groups[i].Name,
|
||||
CreatedTimestamp: &groups[i].Created.Time,
|
||||
ModifiedTimestamp: &groups[i].Modified.Time,
|
||||
})
|
||||
}
|
||||
jsonGroups, err := json.MarshalIndent(outputGroups, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonGroups))
|
||||
} else {
|
||||
data := pterm.TableData{columns}
|
||||
|
||||
for _, resource := range resources {
|
||||
for _, group := range groups {
|
||||
entry := make([]string, len(columns))
|
||||
for i := range columns {
|
||||
switch strings.ToLower(columns[i]) {
|
||||
case "id":
|
||||
entry[i] = resource.ID
|
||||
entry[i] = group.ID
|
||||
case "name":
|
||||
entry[i] = shellescape.StripUnsafe(resource.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])
|
||||
|
@ -82,5 +117,6 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,9 +2,13 @@ package keepass
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"syscall"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
|
@ -13,7 +17,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"github.com/tobischo/gokeepasslib/v3"
|
||||
w "github.com/tobischo/gokeepasslib/v3/wrappers"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// KeepassExportCmd Exports a Passbolt Keepass
|
||||
|
@ -55,13 +58,12 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
|||
cmd.SilenceUsage = true
|
||||
|
||||
if keepassPassword == "" {
|
||||
fmt.Print("Enter Keepass Password:")
|
||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||
pw, err := util.ReadPassword("Enter Keepass Password:")
|
||||
if err != nil {
|
||||
fmt.Println()
|
||||
return fmt.Errorf("Reading Keepass Password: %w", err)
|
||||
}
|
||||
keepassPassword = string(bytepw)
|
||||
keepassPassword = pw
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
|
@ -91,22 +93,15 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Progress: %w", err)
|
||||
}
|
||||
|
||||
for i, resource := range resources {
|
||||
_, _, _, _, pass, desc, err := helper.GetResourceFromData(client, resource, resource.Secrets[0], resource.ResourceType)
|
||||
for _, resource := range resources {
|
||||
entry, err := getKeepassEntry(client, resource, resource.Secrets[0], resource.ResourceType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Get Resource %v, %v %w", i, resource.ID, err)
|
||||
fmt.Printf("\nSkipping Export of Resource %v %v Because of: %v\n", resource.ID, resource.Name, err)
|
||||
progressbar.Increment()
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
rootGroup.Entries = append(rootGroup.Entries, *entry)
|
||||
progressbar.Increment()
|
||||
}
|
||||
|
||||
|
@ -133,3 +128,102 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
|||
|
||||
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,7 +1,10 @@
|
|||
package main
|
||||
|
||||
import "github.com/passbolt/go-passbolt-cli/cmd"
|
||||
import (
|
||||
"github.com/passbolt/go-passbolt-cli/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.SetVersionInfo(version, commit, date, dirty)
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package resource
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
|
@ -54,6 +55,10 @@ func ResourceCreate(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -78,6 +83,18 @@ func ResourceCreate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Creating Resource: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
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
|
||||
}
|
||||
|
|
80
resource/filter.go
Normal file
80
resource/filter.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
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
|
||||
}
|
128
resource/get.go
128
resource/get.go
|
@ -2,11 +2,16 @@ package resource
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/helper"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -18,10 +23,24 @@ var ResourceGetCmd = &cobra.Command{
|
|||
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() {
|
||||
ResourceGetCmd.Flags().String("id", "", "id of Resource to Get")
|
||||
|
||||
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 {
|
||||
|
@ -29,6 +48,10 @@ func ResourceGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -47,11 +70,114 @@ func ResourceGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("Getting Resource: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
jsonResource, err := json.MarshalIndent(ResourceJsonOutput{
|
||||
FolderParentID: &folderParentID,
|
||||
Name: &name,
|
||||
Username: &username,
|
||||
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
|
||||
}
|
||||
|
|
26
resource/json.go
Normal file
26
resource/json.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
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"`
|
||||
}
|
|
@ -2,10 +2,12 @@ package resource
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
"github.com/passbolt/go-passbolt/helper"
|
||||
|
@ -26,11 +28,9 @@ var ResourceListCmd = &cobra.Command{
|
|||
func init() {
|
||||
ResourceListCmd.Flags().Bool("favorite", false, "Resources that are marked as favorite")
|
||||
ResourceListCmd.Flags().Bool("own", false, "Resources that are owned by me")
|
||||
|
||||
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("column", "c", []string{"ID", "FolderParentID", "Name", "Username", "URI"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, Username, URI, Password, Description")
|
||||
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")
|
||||
}
|
||||
|
||||
func ResourceList(cmd *cobra.Command, args []string) error {
|
||||
|
@ -57,6 +57,14 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
|||
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
|
||||
}
|
||||
celFilter, err := cmd.Flags().GetString("filter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -77,6 +85,36 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Listing Resource: %w", err)
|
||||
}
|
||||
|
||||
resources, err = filterResources(&resources, celFilter, ctx, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputResources := []ResourceJsonOutput{}
|
||||
for i := range resources {
|
||||
_, _, _, _, pass, desc, err := helper.GetResource(ctx, client, resources[i].ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Get Resource %w", err)
|
||||
}
|
||||
outputResources = append(outputResources, ResourceJsonOutput{
|
||||
ID: &resources[i].ID,
|
||||
FolderParentID: &resources[i].FolderParentID,
|
||||
Name: &resources[i].Name,
|
||||
Username: &resources[i].Username,
|
||||
URI: &resources[i].URI,
|
||||
Password: &pass,
|
||||
Description: &desc,
|
||||
CreatedTimestamp: &resources[i].Created.Time,
|
||||
ModifiedTimestamp: &resources[i].Modified.Time,
|
||||
})
|
||||
}
|
||||
jsonResources, err := json.MarshalIndent(outputResources, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonResources))
|
||||
} else {
|
||||
data := pterm.TableData{columns}
|
||||
|
||||
for _, resource := range resources {
|
||||
|
@ -105,6 +143,10 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
|||
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])
|
||||
|
@ -114,5 +156,6 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
|
@ -45,6 +46,11 @@ func UserCreate(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
client, err := util.GetClient(ctx)
|
||||
|
@ -66,6 +72,18 @@ func UserCreate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Creating User: %w", err)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
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
|
||||
}
|
||||
|
|
60
user/filter.go
Normal file
60
user/filter.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
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
|
||||
}
|
21
user/get.go
21
user/get.go
|
@ -2,9 +2,10 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/helper"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -29,6 +30,10 @@ func UserGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -47,10 +52,22 @@ func UserGet(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("Getting User: %w", err)
|
||||
}
|
||||
if jsonOutput {
|
||||
jsonUser, err := json.MarshalIndent(UserJsonOutput{
|
||||
Username: &username,
|
||||
FirstName: &firstname,
|
||||
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
|
||||
}
|
||||
|
|
13
user/json.go
Normal file
13
user/json.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
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"`
|
||||
}
|
43
user/list.go
43
user/list.go
|
@ -2,10 +2,12 @@ package user
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"al.essio.dev/pkg/shellescape"
|
||||
"github.com/passbolt/go-passbolt-cli/util"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -29,7 +31,7 @@ func init() {
|
|||
UserListCmd.Flags().StringP("search", "s", "", "Search for Users")
|
||||
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")
|
||||
UserListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Username", "FirstName", "LastName", "Role"}, "Columns to return, possible Columns:\nID, Username, FirstName, LastName, Role, CreatedTimestamp, ModifiedTimestamp")
|
||||
}
|
||||
|
||||
func UserList(cmd *cobra.Command, args []string) error {
|
||||
|
@ -56,6 +58,14 @@ func UserList(cmd *cobra.Command, args []string) error {
|
|||
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
|
||||
}
|
||||
celFilter, err := cmd.Flags().GetString("filter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := util.GetContext()
|
||||
|
||||
|
@ -76,6 +86,30 @@ func UserList(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Listing User: %w", err)
|
||||
}
|
||||
|
||||
users, err = filterUsers(&users, celFilter, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
outputUsers := []UserJsonOutput{}
|
||||
for i := range users {
|
||||
outputUsers = append(outputUsers, UserJsonOutput{
|
||||
ID: &users[i].ID,
|
||||
Username: &users[i].Username,
|
||||
FirstName: &users[i].Profile.FirstName,
|
||||
LastName: &users[i].Profile.LastName,
|
||||
Role: &users[i].Role.Name,
|
||||
CreatedTimestamp: &users[i].Created.Time,
|
||||
ModifiedTimestamp: &users[i].Modified.Time,
|
||||
})
|
||||
}
|
||||
jsonUsers, err := json.MarshalIndent(outputUsers, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(jsonUsers))
|
||||
} else {
|
||||
data := pterm.TableData{columns}
|
||||
|
||||
for _, user := range users {
|
||||
|
@ -92,6 +126,10 @@ func UserList(cmd *cobra.Command, args []string) error {
|
|||
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])
|
||||
|
@ -101,5 +139,6 @@ func UserList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
23
util/cel.go
Normal file
23
util/cel.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
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,12 +1,15 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
"github.com/passbolt/go-passbolt/helper"
|
||||
|
@ -14,6 +17,30 @@ import (
|
|||
"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
|
||||
func GetClient(ctx context.Context) (*api.Client, error) {
|
||||
serverAddress := viper.GetString("serverAddress")
|
||||
|
@ -28,17 +55,21 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
|||
|
||||
userPassword := viper.GetString("userPassword")
|
||||
if userPassword == "" {
|
||||
fmt.Print("Enter Password:")
|
||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||
cliPassword, err := ReadPassword("Enter Password:")
|
||||
if err != nil {
|
||||
fmt.Println()
|
||||
return nil, fmt.Errorf("Reading Password: %w", err)
|
||||
}
|
||||
userPassword = string(bytepw)
|
||||
|
||||
userPassword = cliPassword
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
client, err := api.NewClient(nil, "", serverAddress, userPrivateKey, userPassword)
|
||||
httpClient, err := GetHttpClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating Client: %w", err)
|
||||
}
|
||||
|
@ -58,32 +89,30 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
|||
switch viper.GetString("mfaMode") {
|
||||
case "interactive-totp":
|
||||
client.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
|
||||
challange := api.MFAChallange{}
|
||||
err := json.Unmarshal(res.Body, &challange)
|
||||
challenge := api.MFAChallenge{}
|
||||
err := json.Unmarshal(res.Body, &challenge)
|
||||
if err != nil {
|
||||
return http.Cookie{}, fmt.Errorf("Parsing MFA Challange")
|
||||
return http.Cookie{}, fmt.Errorf("Parsing MFA Challenge")
|
||||
}
|
||||
if challange.Provider.TOTP == "" {
|
||||
if challenge.Provider.TOTP == "" {
|
||||
return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider")
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
var code string
|
||||
fmt.Print("Enter TOTP:")
|
||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
||||
code, err := ReadPassword("Enter TOTP:")
|
||||
if err != nil {
|
||||
fmt.Printf("\n")
|
||||
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
|
||||
}
|
||||
code = string(bytepw)
|
||||
fmt.Printf("\n")
|
||||
req := api.MFAChallangeResponse{
|
||||
req := api.MFAChallengeResponse{
|
||||
TOTP: code,
|
||||
}
|
||||
var raw *http.Response
|
||||
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
|
||||
if err != nil {
|
||||
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
|
||||
return http.Cookie{}, fmt.Errorf("Doing MFA Challange Response: %w", err)
|
||||
return http.Cookie{}, fmt.Errorf("Doing MFA Challenge Response: %w", err)
|
||||
}
|
||||
fmt.Println("TOTP Verification Failed")
|
||||
} else {
|
||||
|
@ -96,10 +125,21 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
|||
return http.Cookie{}, fmt.Errorf("Unable to find Passbolt MFA Cookie")
|
||||
}
|
||||
}
|
||||
return http.Cookie{}, fmt.Errorf("Failed MFA Challange 3 times: %w", err)
|
||||
return http.Cookie{}, fmt.Errorf("Failed MFA Challenge 3 times: %w", err)
|
||||
}
|
||||
case "noninteractive-totp":
|
||||
helper.AddMFACallbackTOTP(client, viper.GetUint("mfaRetrys"), viper.GetDuration("mfaDelay"), viper.GetDuration("totpOffset"), viper.GetString("totpToken"))
|
||||
// if new flag is unset, use old flag instead
|
||||
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":
|
||||
default:
|
||||
}
|
||||
|
|
44
util/http.go
Normal file
44
util/http.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
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
Normal file
41
version.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
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