Compare commits

..

No commits in common. "main" and "v0.1.7" have entirely different histories.
main ... v0.1.7

56 changed files with 1105 additions and 1943 deletions

View file

@ -1,27 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: speatzle
---
**Describe the bug:**
A clear and concise description of what the bug is.
**To Reproduce:**
A clear and concise description of how to reproduce the bug.
**Output when using --debug (you should censor this):**
**Passbolt Server Version (please complete the following information):**
- Edition: [e.g. Cloud / PRO / Community]
- Version [e.g. 3.5.0]
**go-passbolt-cli Version (please complete the following information):**
- OS: [e.g. linux]
- Version [e.g. v0.1.7]
**Additional context**
Add any other context about the problem here.

View file

@ -14,7 +14,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.21
go-version: 1.17
- name: Build
run: go build -o passbolt

View file

@ -13,45 +13,32 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: 1.21
go-version: 1.17
-
name: Generate Man and Completions
run: |
mkdir completion
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
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
mkdir man
go run *.go gendoc --type man
go run main.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@v5
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
version: latest
args: release --clean ${{ env.flags }}
args: release --rm-dist
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/

View file

@ -14,7 +14,13 @@ builds:
- arm64
binary: passbolt
archives:
- files:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
files:
- completion/*
- man/*
format_overrides:
@ -25,9 +31,9 @@ release:
header: |
## Release {{ .Tag }} - ({{ .Date }})
nfpms:
- maintainer: Samuel Lorch <sam@soontm.de>
- maintainer: Samuel Lorch <sam@lorch.net>
description: A CLI for Passbolt.
homepage: https://github.com/passbolt/go-passbolt-cli
homepage: https://github.com/speatzle/go-passbolt-cli
license: MIT
contents:
- src: /home/runner/work/go-passbolt-cli/go-passbolt-cli/completion/bash
@ -43,22 +49,3 @@ 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 }}"

View file

@ -1,30 +1,22 @@
# 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/speatzle/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
# Install
## Via Repository (Prefered):
[![Packaging status](https://repology.org/badge/vertical-allrepos/go:passbolt-cli.svg)](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.
## Via Package (Prefered):
Download the Package for your OS 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@latest
go install github.com/speatzle/go-passbolt-cli
Note: this will install the binary as go-passbolt-cli, also tab completion and manpages will be missing.
# Getting Started
@ -38,35 +30,34 @@ or
```
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
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 permanently this ways
- MFA settings can also be save permenantly this ways
# Usage
Generally the Structure of Commands is like this:
```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.
Entity is a Resource(Password), Folder, User or Group that you want to apply an action to.
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.
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
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:
```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.
@ -82,43 +73,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:
```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
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 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 |
| --- | --- |
|`none`|just errors if challenged for MFA.
|`none`|just errors if challanged for MFA.
|`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
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.
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
# 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/speatzle/go-passbolt-cli/wiki/go-passbolt-cli).
And is also available via `man passbolt`

View file

@ -1,10 +1,10 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/group"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/passbolt/go-passbolt-cli/user"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/group"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/user"
"github.com/spf13/cobra"
)
@ -18,7 +18,6 @@ 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)

View file

@ -1,10 +1,10 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/group"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/passbolt/go-passbolt-cli/user"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/group"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/user"
"github.com/spf13/cobra"
)

View file

@ -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
}

View file

@ -1,18 +0,0 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/keepass"
"github.com/spf13/cobra"
)
// exportCmd represents the export command
var exportCmd = &cobra.Command{
Use: "export",
Short: "Exports Passbolt Data",
Long: `Exports Passbolt Data`,
}
func init() {
rootCmd.AddCommand(exportCmd)
exportCmd.AddCommand(keepass.KeepassExportCmd)
}

View file

@ -1,10 +1,10 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/group"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/passbolt/go-passbolt-cli/user"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/group"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/user"
"github.com/spf13/cobra"
)
@ -18,7 +18,6 @@ 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)

View file

@ -1,10 +1,10 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/group"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/passbolt/go-passbolt-cli/user"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/group"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/user"
"github.com/spf13/cobra"
)
@ -18,13 +18,6 @@ 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)

View file

@ -1,8 +1,8 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/spf13/cobra"
)

View file

@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"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("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"))
@ -72,27 +60,9 @@ 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.
@ -128,32 +98,16 @@ func initConfig() {
// Read in Private Key from File if userprivatekeyfile is set
userprivatekeyfile, err := rootCmd.PersistentFlags().GetString("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") {
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
}

View file

@ -1,8 +1,8 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/spf13/cobra"
)

View file

@ -1,10 +1,10 @@
package cmd
import (
"github.com/passbolt/go-passbolt-cli/folder"
"github.com/passbolt/go-passbolt-cli/group"
"github.com/passbolt/go-passbolt-cli/resource"
"github.com/passbolt/go-passbolt-cli/user"
"github.com/speatzle/go-passbolt-cli/folder"
"github.com/speatzle/go-passbolt-cli/group"
"github.com/speatzle/go-passbolt-cli/resource"
"github.com/speatzle/go-passbolt-cli/user"
"github.com/spf13/cobra"
)

View file

@ -2,11 +2,13 @@ package cmd
import (
"fmt"
"syscall"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/api"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/api"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
)
// verifyCMD represents the verify command
@ -32,20 +34,17 @@ var verifyCMD = &cobra.Command{
userPassword := viper.GetString("userPassword")
if userPassword == "" {
pw, err := util.ReadPassword("Enter Password:")
fmt.Print("Enter Password:")
bytepw, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Println()
return fmt.Errorf("Reading Password: %w", err)
}
userPassword = pw
userPassword = string(bytepw)
fmt.Println()
}
httpClient, err := util.GetHttpClient()
if err != nil {
return err
}
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
client, err := api.NewClient(nil, "", serverAddress, userPrivateKey, userPassword)
if err != nil {
return fmt.Errorf("Creating Client: %w", err)
}

View file

@ -2,11 +2,10 @@ package folder
import (
"context"
"encoding/json"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -34,10 +33,6 @@ 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()
@ -58,18 +53,6 @@ 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
}

View file

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/spf13/cobra"
)

View file

@ -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
}

View file

@ -2,11 +2,11 @@ package folder
import (
"context"
"encoding/json"
"fmt"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -29,10 +29,6 @@ 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()
@ -43,22 +39,15 @@ func FolderGet(cmd *cobra.Command, args []string) error {
defer client.Logout(context.TODO())
cmd.SilenceUsage = true
folder, err := client.GetFolder(ctx, id, nil)
folderParentID, name, err := helper.GetFolder(
ctx,
client,
id,
)
if err != nil {
return fmt.Errorf("Getting Folder: %w", err)
}
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))
}
fmt.Printf("FolderParentID: %v\n", folderParentID)
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
return nil
}

View file

@ -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"`
}

View file

@ -2,14 +2,12 @@ package folder
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/api"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/api"
"github.com/spf13/cobra"
"github.com/pterm/pterm"
@ -28,7 +26,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, 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 {
@ -47,14 +45,6 @@ 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
@ -73,28 +63,6 @@ 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 {
@ -107,10 +75,6 @@ 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])
@ -120,6 +84,5 @@ func FolderList(cmd *cobra.Command, args []string) error {
}
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
}
return nil
}

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

74
go.mod
View file

@ -1,67 +1,17 @@
module github.com/passbolt/go-passbolt-cli
module github.com/speatzle/go-passbolt-cli
go 1.23.0
toolchain go1.23.6
go 1.16
require (
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
github.com/alessio/shellescape v1.4.1
github.com/gookit/color v1.5.0 // indirect
github.com/pterm/pterm v0.12.34
github.com/speatzle/go-passbolt v0.5.3
github.com/spf13/afero v1.8.0 // indirect
github.com/spf13/cobra v1.3.0
github.com/spf13/viper v1.10.1
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/ini.v1 v1.66.3 // indirect
)
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
// replace github.com/speatzle/go-passbolt => ../go-passbolt

1019
go.sum

File diff suppressed because it is too large Load diff

View file

@ -2,11 +2,10 @@ package group
import (
"context"
"encoding/json"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -41,10 +40,6 @@ 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 {
@ -79,18 +74,6 @@ 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
}

View file

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/spf13/cobra"
)

View file

@ -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
}

View file

@ -2,14 +2,13 @@ package group
import (
"context"
"encoding/json"
"fmt"
"strings"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/alessio/shellescape"
"github.com/pterm/pterm"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -38,10 +37,6 @@ 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()
@ -60,29 +55,6 @@ 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 {
@ -112,6 +84,5 @@ func GroupGet(cmd *cobra.Command, args []string) error {
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
}
}
return nil
}

View file

@ -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"`
}

View file

@ -2,14 +2,12 @@ package group
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/api"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/api"
"github.com/spf13/cobra"
"github.com/pterm/pterm"
@ -28,7 +26,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, 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 {
@ -47,14 +45,6 @@ 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()
@ -65,7 +55,7 @@ func GroupList(cmd *cobra.Command, args []string) error {
defer client.Logout(context.TODO())
cmd.SilenceUsage = true
groups, err := client.GetGroups(ctx, &api.GetGroupsOptions{
resources, err := client.GetGroups(ctx, &api.GetGroupsOptions{
FilterHasUsers: users,
FilterHasManagers: managers,
})
@ -73,41 +63,16 @@ 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 _, group := range groups {
for _, resource := range resources {
entry := make([]string, len(columns))
for i := range columns {
switch strings.ToLower(columns[i]) {
case "id":
entry[i] = group.ID
entry[i] = resource.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)
entry[i] = shellescape.StripUnsafe(resource.Name)
default:
cmd.SilenceUsage = false
return fmt.Errorf("Unknown Column: %v", columns[i])
@ -117,6 +82,5 @@ func GroupList(cmd *cobra.Command, args []string) error {
}
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
}
return nil
}

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -1,229 +0,0 @@
package keepass
import (
"context"
"encoding/json"
"fmt"
"net/url"
"os"
"sort"
"strconv"
"strings"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/api"
"github.com/passbolt/go-passbolt/helper"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/tobischo/gokeepasslib/v3"
w "github.com/tobischo/gokeepasslib/v3/wrappers"
)
// KeepassExportCmd Exports a Passbolt Keepass
var KeepassExportCmd = &cobra.Command{
Use: "keepass",
Short: "Exports Passbolt to a Keepass File",
Long: `Exports Passbolt to a Keepass File`,
Aliases: []string{},
RunE: KeepassExport,
}
func init() {
KeepassExportCmd.Flags().StringP("file", "f", "passbolt-export.kdbx", "File name of the Keepass File")
KeepassExportCmd.Flags().StringP("password", "p", "", "Password for the Keypass File, if empty prompts interactively")
}
func KeepassExport(cmd *cobra.Command, args []string) error {
filename, err := cmd.Flags().GetString("file")
if err != nil {
return err
}
if filename == "" {
return fmt.Errorf("the Filename cannot be empty")
}
keepassPassword, err := cmd.Flags().GetString("password")
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
if keepassPassword == "" {
pw, err := util.ReadPassword("Enter Keepass Password:")
if err != nil {
fmt.Println()
return fmt.Errorf("Reading Keepass Password: %w", err)
}
keepassPassword = pw
fmt.Println()
}
fmt.Println("Getting Resources...")
resources, err := client.GetResources(ctx, &api.GetResourcesOptions{
ContainSecret: true,
ContainResourceType: true,
ContainTags: true,
})
if err != nil {
return fmt.Errorf("Getting Resources: %w", err)
}
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("Creating File: %w", err)
}
defer file.Close()
rootGroup := gokeepasslib.NewGroup()
rootGroup.Name = "root"
pterm.EnableStyling()
pterm.DisableColor()
progressbar, err := pterm.DefaultProgressbar.WithTitle("Decryping Resources").WithTotal(len(resources)).Start()
if err != nil {
return fmt.Errorf("Progress: %w", err)
}
for _, resource := range resources {
entry, err := getKeepassEntry(client, resource, resource.Secrets[0], resource.ResourceType)
if err != nil {
fmt.Printf("\nSkipping Export of Resource %v %v Because of: %v\n", resource.ID, resource.Name, err)
progressbar.Increment()
continue
}
rootGroup.Entries = append(rootGroup.Entries, *entry)
progressbar.Increment()
}
db := gokeepasslib.NewDatabase(
gokeepasslib.WithDatabaseKDBXVersion4(),
)
db.Content.Meta.DatabaseName = "Passbolt Export"
if keepassPassword != "" {
db.Credentials = gokeepasslib.NewPasswordCredentials(keepassPassword)
}
db.Content.Root = &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{rootGroup},
}
db.LockProtectedEntries()
keepassEncoder := gokeepasslib.NewEncoder(file)
if err := keepassEncoder.Encode(db); err != nil {
return fmt.Errorf("Encodeing kdbx: %w", err)
}
fmt.Println("Done")
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()
}

View file

@ -1,10 +1,7 @@
package main
import (
"github.com/passbolt/go-passbolt-cli/cmd"
)
import "github.com/speatzle/go-passbolt-cli/cmd"
func main() {
cmd.SetVersionInfo(version, commit, date, dirty)
cmd.Execute()
}

View file

@ -2,11 +2,10 @@ package resource
import (
"context"
"encoding/json"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -55,10 +54,6 @@ 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()
@ -83,18 +78,6 @@ 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
}

View file

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/spf13/cobra"
)

View file

@ -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
}

View file

@ -2,16 +2,11 @@ package resource
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/pterm/pterm"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -23,24 +18,10 @@ 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 {
@ -48,10 +29,6 @@ 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()
@ -70,114 +47,11 @@ 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
}

View file

@ -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"`
}

View file

@ -2,15 +2,13 @@ package resource
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/api"
"github.com/passbolt/go-passbolt/helper"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/api"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
"github.com/pterm/pterm"
@ -28,9 +26,11 @@ 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("group", "g", []string{}, "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, 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 {
@ -42,7 +42,7 @@ func ResourceList(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
group, err := cmd.Flags().GetString("group")
groups, err := cmd.Flags().GetStringArray("group")
if err != nil {
return err
}
@ -57,14 +57,6 @@ 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()
@ -78,43 +70,13 @@ func ResourceList(cmd *cobra.Command, args []string) error {
resources, err := client.GetResources(ctx, &api.GetResourcesOptions{
FilterIsFavorite: favorite,
FilterIsOwnedByMe: own,
FilterIsSharedWithGroup: group,
FilterIsSharedWithGroup: groups,
FilterHasParent: folderParents,
})
if err != nil {
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 {
@ -143,10 +105,6 @@ 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])
@ -156,6 +114,5 @@ func ResourceList(cmd *cobra.Command, args []string) error {
}
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
}
return nil
}

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -2,11 +2,10 @@ package user
import (
"context"
"encoding/json"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -46,11 +45,6 @@ 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)
@ -72,18 +66,6 @@ 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
}

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -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
}

View file

@ -2,12 +2,11 @@ package user
import (
"context"
"encoding/json"
"fmt"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)
@ -30,10 +29,6 @@ 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()
@ -52,22 +47,10 @@ 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
}

View file

@ -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"`
}

View file

@ -2,14 +2,12 @@ package user
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"al.essio.dev/pkg/shellescape"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/api"
"github.com/alessio/shellescape"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/api"
"github.com/spf13/cobra"
"github.com/pterm/pterm"
@ -31,7 +29,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, 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 {
@ -58,14 +56,6 @@ 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()
@ -86,30 +76,6 @@ 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 {
@ -126,10 +92,6 @@ 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])
@ -139,6 +101,5 @@ func UserList(cmd *cobra.Command, args []string) error {
}
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
}
return nil
}

View file

@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/passbolt/go-passbolt-cli/util"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt-cli/util"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/cobra"
)

View file

@ -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
}

View file

@ -1,46 +1,19 @@
package util
import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"
"syscall"
"github.com/passbolt/go-passbolt/api"
"github.com/passbolt/go-passbolt/helper"
"github.com/speatzle/go-passbolt/api"
"github.com/speatzle/go-passbolt/helper"
"github.com/spf13/viper"
"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")
@ -55,21 +28,17 @@ func GetClient(ctx context.Context) (*api.Client, error) {
userPassword := viper.GetString("userPassword")
if userPassword == "" {
cliPassword, err := ReadPassword("Enter Password:")
fmt.Print("Enter Password:")
bytepw, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Println()
return nil, fmt.Errorf("Reading Password: %w", err)
}
userPassword = cliPassword
userPassword = string(bytepw)
fmt.Println()
}
httpClient, err := GetHttpClient()
if err != nil {
return nil, err
}
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
client, err := api.NewClient(nil, "", serverAddress, userPrivateKey, userPassword)
if err != nil {
return nil, fmt.Errorf("Creating Client: %w", err)
}
@ -89,30 +58,32 @@ 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) {
challenge := api.MFAChallenge{}
err := json.Unmarshal(res.Body, &challenge)
challange := api.MFAChallange{}
err := json.Unmarshal(res.Body, &challange)
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")
}
for i := 0; i < 3; i++ {
var code string
code, err := ReadPassword("Enter TOTP:")
fmt.Print("Enter TOTP:")
bytepw, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Printf("\n")
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
}
code = string(bytepw)
fmt.Printf("\n")
req := api.MFAChallengeResponse{
req := api.MFAChallangeResponse{
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 Challenge Response: %w", err)
return http.Cookie{}, fmt.Errorf("Doing MFA Challange Response: %w", err)
}
fmt.Println("TOTP Verification Failed")
} 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("Failed MFA Challenge 3 times: %w", err)
return http.Cookie{}, fmt.Errorf("Failed MFA Challange 3 times: %w", err)
}
case "noninteractive-totp":
// 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)
helper.AddMFACallbackTOTP(client, viper.GetUint("mfaRetrys"), viper.GetDuration("mfaDelay"), viper.GetDuration("totpOffset"), viper.GetString("totpToken"))
case "none":
default:
}

View file

@ -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
}

View file

@ -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"
}
}
}
}