mirror of
https://github.com/passbolt/go-passbolt-cli.git
synced 2025-05-12 10:58:21 +00:00
Compare commits
117 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 | ||
![]() |
4768cd1333 | ||
![]() |
14f189261a | ||
![]() |
5989fa2998 | ||
![]() |
95644e205e | ||
![]() |
f49471e31e | ||
![]() |
7b35d8dac7 | ||
![]() |
c8c7203ef0 | ||
![]() |
f4dedc8726 | ||
![]() |
ab3e63835a | ||
![]() |
f7aa2b232b | ||
![]() |
0e991f33be | ||
![]() |
138c8ae152 |
56 changed files with 1956 additions and 1102 deletions
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
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.
|
2
.github/workflows/.docs.yml
vendored
2
.github/workflows/.docs.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.21
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -o passbolt
|
run: go build -o passbolt
|
||||||
|
|
33
.github/workflows/.release.yml
vendored
33
.github/workflows/.release.yml
vendored
|
@ -13,32 +13,45 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
-
|
-
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.21
|
||||||
-
|
-
|
||||||
name: Generate Man and Completions
|
name: Generate Man and Completions
|
||||||
run: |
|
run: |
|
||||||
mkdir completion
|
mkdir completion
|
||||||
go run main.go completion bash > completion/bash
|
go run *.go completion bash > completion/bash
|
||||||
go run main.go completion zsh > completion/zsh
|
go run *.go completion zsh > completion/zsh
|
||||||
go run main.go completion fish > completion/fish
|
go run *.go completion fish > completion/fish
|
||||||
go run main.go completion powershell > completion/powershell
|
go run *.go completion powershell > completion/powershell
|
||||||
mkdir man
|
mkdir man
|
||||||
go run main.go gendoc --type man
|
go run *.go gendoc --type man
|
||||||
pwd
|
pwd
|
||||||
ls
|
ls
|
||||||
|
-
|
||||||
|
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||||
-
|
-
|
||||||
name: Run GoReleaser
|
name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist
|
args: release --clean ${{ env.flags }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Run Version
|
||||||
|
run: |
|
||||||
|
dist/go-passbolt-cli_linux_amd64_v1/passbolt -v
|
||||||
|
-
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: go-passbolt-cli-artifacts
|
||||||
|
path: dist/
|
||||||
|
|
|
@ -14,13 +14,7 @@ builds:
|
||||||
- arm64
|
- arm64
|
||||||
binary: passbolt
|
binary: passbolt
|
||||||
archives:
|
archives:
|
||||||
- replacements:
|
- files:
|
||||||
darwin: Darwin
|
|
||||||
linux: Linux
|
|
||||||
windows: Windows
|
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
files:
|
|
||||||
- completion/*
|
- completion/*
|
||||||
- man/*
|
- man/*
|
||||||
format_overrides:
|
format_overrides:
|
||||||
|
@ -31,9 +25,9 @@ release:
|
||||||
header: |
|
header: |
|
||||||
## Release {{ .Tag }} - ({{ .Date }})
|
## Release {{ .Tag }} - ({{ .Date }})
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: Samuel Lorch <sam@lorch.net>
|
- maintainer: Samuel Lorch <sam@soontm.de>
|
||||||
description: A CLI for Passbolt.
|
description: A CLI for Passbolt.
|
||||||
homepage: https://github.com/speatzle/go-passbolt-cli
|
homepage: https://github.com/passbolt/go-passbolt-cli
|
||||||
license: MIT
|
license: MIT
|
||||||
contents:
|
contents:
|
||||||
- src: /home/runner/work/go-passbolt-cli/go-passbolt-cli/completion/bash
|
- src: /home/runner/work/go-passbolt-cli/go-passbolt-cli/completion/bash
|
||||||
|
@ -49,3 +43,22 @@ nfpms:
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
brews:
|
||||||
|
- homepage: https://github.com/passbolt/go-passbolt-cli
|
||||||
|
license: "MIT"
|
||||||
|
skip_upload: false
|
||||||
|
description: "A CLI tool to interact with Passbolt, a Open source Password Manager for Teams"
|
||||||
|
directory: Formula
|
||||||
|
install: |
|
||||||
|
bin.install "passbolt"
|
||||||
|
bash_completion.install "completion/bash" => "passbolt"
|
||||||
|
zsh_completion.install "completion/zsh" => "_passbolt"
|
||||||
|
fish_completion.install "completion/fish" => "passbolt.fish"
|
||||||
|
man1.install Dir["man/*"]
|
||||||
|
# ...
|
||||||
|
repository:
|
||||||
|
owner: passbolt
|
||||||
|
name: homebrew-tap
|
||||||
|
token: "{{ .Env.TAP_GITHUB_TOKEN }}"
|
||||||
|
|
||||||
|
|
||||||
|
|
63
README.md
63
README.md
|
@ -1,22 +1,30 @@
|
||||||
# go-passbolt-cli
|
# 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/speatzle/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
|
Disclaimer: This project is community driven and not associated with Passbolt SA
|
||||||
# Install
|
# Install
|
||||||
|
|
||||||
## Via Package (Prefered):
|
## Via Repository (Prefered):
|
||||||
Download the Package for your OS and architecture from the Latest Release.
|
[](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`
|
Install via your Distros Package manager like `dpkg -i`
|
||||||
|
|
||||||
|
## Via Homebrew
|
||||||
|
brew install passbolt/tap/go-passbolt-cli
|
||||||
|
|
||||||
## Via Archive:
|
## Via Archive:
|
||||||
Download and Extract the Archive for your OS and architecture from the Latest Release.
|
Download and Extract the Archive for your OS and architecture from the Latest Release.
|
||||||
Note: tab completion and manpages will need to be installed manually.
|
Note: tab completion and manpages will need to be installed manually.
|
||||||
|
|
||||||
## Via Go:
|
## Via Go:
|
||||||
go install github.com/speatzle/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.
|
Note: this will install the binary as go-passbolt-cli, also tab completion and manpages will be missing.
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
@ -30,34 +38,35 @@ or
|
||||||
```
|
```
|
||||||
passbolt configure --serverAddress https://passbolt.example.org --userPassword '1234' --userPrivateKey '-----BEGIN PGP PRIVATE KEY BLOCK-----'
|
passbolt configure --serverAddress https://passbolt.example.org --userPassword '1234' --userPrivateKey '-----BEGIN PGP PRIVATE KEY BLOCK-----'
|
||||||
```
|
```
|
||||||
- Setup Enviroment Variables
|
- Setup Environment Variables
|
||||||
- Provide the Flags manually every time
|
- Provide the Flags manually every time
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- You can set the Private Key using the flags `--userPrivateKey` or `--userPrivateKeyFile` where `--userPrivateKey` takes the actual private key and `--userPrivateKeyFile` loads the content of a file as the PrivateKey, `--userPrivateKeyFile` overwrites the value of `--userPrivateKey`.
|
- You can set the Private Key using the flags `--userPrivateKey` or `--userPrivateKeyFile` where `--userPrivateKey` takes the actual private key and `--userPrivateKeyFile` loads the content of a file as the PrivateKey, `--userPrivateKeyFile` overwrites the value of `--userPrivateKey`.
|
||||||
- You can also just store the serverAddress and your Private Key, if your Password is not set it will prompt you for it every time.
|
- You can also just store the serverAddress and your Private Key, if your Password is not set it will prompt you for it every time.
|
||||||
- MFA settings can also be save permenantly this ways
|
- Passwordless PrivateKeys are unsupported
|
||||||
|
- MFA settings can also be save permanently this ways
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
Generally the Structure of Commands is like this:
|
Generally the Structure of Commands is like this:
|
||||||
```bash
|
```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.
|
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 a action to.
|
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.
|
In Passbolt a Password is usually revert to as a Resource.
|
||||||
|
|
||||||
To Create a Resource you can do this, it will return the ID of the newly created Resource:
|
To Create a Resource you can do this, it will return the ID of the newly created Resource:
|
||||||
```bash
|
```bash
|
||||||
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:
|
You can then list all users:
|
||||||
```bash
|
```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.
|
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.
|
||||||
|
|
||||||
|
@ -73,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:
|
Now that we have a Resource ID, know the ID's of other Users and about know about Permission Types, we can share the Resource with them:
|
||||||
```bash
|
```bash
|
||||||
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
|
Note: you can supply the the users argument multiple times to share with multiple users
|
||||||
|
|
||||||
For sharing with groups the `--group` argument exists.
|
For sharing with groups the `--group` argument exists.
|
||||||
|
|
||||||
# MFA
|
# MFA
|
||||||
you can setup MFA also using the configuration sub command, only TOTP is supported, there are 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 |
|
| 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.
|
|`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
|
# Server Verification
|
||||||
to enable Server Verification you need to run `passbolt verify` once, after that the server will always be verified if the same config is used
|
To enable Server Verification you need to run `passbolt verify` once, after that the server will always be verified if the same config is used
|
||||||
|
|
||||||
|
# Scripting
|
||||||
|
For Scripting we have a -j or --json flag to convert the Output for the create, get and list commands to JSON for easier Parsing in Scripts.
|
||||||
|
|
||||||
|
Note: The JSON Output does not cover Error Messages, you can detect Errors by checking if the Exitcode is not 0
|
||||||
|
|
||||||
|
# Exposing Secrets to Subprocesses
|
||||||
|
The `exec` command allows you to execute another command with environment variables that reference secrets stored in Passbolt.
|
||||||
|
Any environment variables containing `passbolt://` references are automatically resolved to their corresponding secret values
|
||||||
|
before the specified command is executed. This ensures that secrets are securely injected into the child process's environment
|
||||||
|
without exposing them to the parent shell.
|
||||||
|
For example:
|
||||||
|
```bash
|
||||||
|
export GITHUB_TOKEN=passbolt://<PASSBOLT_RESOURCE_ID_HERE>
|
||||||
|
passbolt exec -- gh auth login
|
||||||
|
```
|
||||||
|
|
||||||
|
This would resolve the passbolt:// reference in GITHUB_TOKEN to its actual secret value and pass it to the gh process.
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
Usage for all Subcommands is [here](https://github.com/speatzle/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`
|
And is also available via `man passbolt`
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/group"
|
"github.com/passbolt/go-passbolt-cli/group"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/speatzle/go-passbolt-cli/user"
|
"github.com/passbolt/go-passbolt-cli/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ var createCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(createCmd)
|
rootCmd.AddCommand(createCmd)
|
||||||
|
createCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
||||||
createCmd.AddCommand(resource.ResourceCreateCmd)
|
createCmd.AddCommand(resource.ResourceCreateCmd)
|
||||||
createCmd.AddCommand(folder.FolderCreateCmd)
|
createCmd.AddCommand(folder.FolderCreateCmd)
|
||||||
createCmd.AddCommand(group.GroupCreateCmd)
|
createCmd.AddCommand(group.GroupCreateCmd)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/group"
|
"github.com/passbolt/go-passbolt-cli/group"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/speatzle/go-passbolt-cli/user"
|
"github.com/passbolt/go-passbolt-cli/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
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
cmd/export.go
Normal file
18
cmd/export.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/group"
|
"github.com/passbolt/go-passbolt-cli/group"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/speatzle/go-passbolt-cli/user"
|
"github.com/passbolt/go-passbolt-cli/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ var getCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(getCmd)
|
rootCmd.AddCommand(getCmd)
|
||||||
|
getCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
||||||
getCmd.AddCommand(resource.ResourceGetCmd)
|
getCmd.AddCommand(resource.ResourceGetCmd)
|
||||||
getCmd.AddCommand(folder.FolderGetCmd)
|
getCmd.AddCommand(folder.FolderGetCmd)
|
||||||
getCmd.AddCommand(group.GroupGetCmd)
|
getCmd.AddCommand(group.GroupGetCmd)
|
||||||
|
|
15
cmd/list.go
15
cmd/list.go
|
@ -1,10 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/group"
|
"github.com/passbolt/go-passbolt-cli/group"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/speatzle/go-passbolt-cli/user"
|
"github.com/passbolt/go-passbolt-cli/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +18,13 @@ var listCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(listCmd)
|
rootCmd.AddCommand(listCmd)
|
||||||
|
listCmd.PersistentFlags().BoolP("json", "j", false, "Output JSON")
|
||||||
|
listCmd.PersistentFlags().String("filter", "",
|
||||||
|
"Define a CEl expression as filter for any list commands. In the expression, all available columns of subcommand can be used (see -c/--column).\n"+
|
||||||
|
"See also CEl specifications under https://github.com/google/cel-spec.\n"+
|
||||||
|
"Examples:\n"+
|
||||||
|
"\t--filter '(Name == \"SomeName\" || matches(Name, \"RegExpr\")) && URI.startsWith(\"https://auth.\")'\n"+
|
||||||
|
"\t--filter 'Username == \"User\" && CreatedTimestamp > timestamp(\"2022-06-10T00:00:00.000-00:00\")'")
|
||||||
listCmd.AddCommand(resource.ResourceListCmd)
|
listCmd.AddCommand(resource.ResourceListCmd)
|
||||||
listCmd.AddCommand(folder.FolderListCmd)
|
listCmd.AddCommand(folder.FolderListCmd)
|
||||||
listCmd.AddCommand(group.GroupListCmd)
|
listCmd.AddCommand(group.GroupListCmd)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
66
cmd/root.go
66
cmd/root.go
|
@ -2,7 +2,6 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"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("userPrivateKeyFile", "", "Passbolt User Private Key File, if set then the userPrivateKey will be Overwritten with the File Content")
|
||||||
rootCmd.PersistentFlags().String("userPassword", "", "Passbolt User Password")
|
rootCmd.PersistentFlags().String("userPassword", "", "Passbolt User Password")
|
||||||
rootCmd.PersistentFlags().String("mfaMode", "interactive-totp", "How to Handle MFA, the following Modes exist: none, interactive-totp and noninteractive-totp")
|
rootCmd.PersistentFlags().String("mfaMode", "interactive-totp", "How to Handle MFA, the following Modes exist: none, interactive-totp and noninteractive-totp")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().String("totpToken", "", "Token to generate TOTP's, only used in nointeractive-totp mode")
|
rootCmd.PersistentFlags().String("totpToken", "", "Token to generate TOTP's, only used in nointeractive-totp mode")
|
||||||
|
rootCmd.PersistentFlags().MarkDeprecated("totpToken", "use --mfaTotpToken instead")
|
||||||
|
rootCmd.PersistentFlags().String("mfaTotpToken", "", "Token to generate TOTP's, only used in nointeractive-totp mode")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().Duration("totpOffset", time.Duration(0), "TOTP Generation offset only used in noninteractive-totp mode")
|
rootCmd.PersistentFlags().Duration("totpOffset", time.Duration(0), "TOTP Generation offset only used in noninteractive-totp mode")
|
||||||
|
rootCmd.PersistentFlags().MarkDeprecated("totpOffset", "use --mfaTotpOffset instead")
|
||||||
|
rootCmd.PersistentFlags().Duration("mfaTotpOffset", time.Duration(0), "TOTP Generation offset only used in noninteractive-totp mode")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().Uint("mfaRetrys", 3, "How often to retry TOTP Auth, only used in nointeractive modes")
|
rootCmd.PersistentFlags().Uint("mfaRetrys", 3, "How often to retry TOTP Auth, only used in nointeractive modes")
|
||||||
rootCmd.PersistentFlags().Duration("mfaDelay", time.Second*10, "Delay between MFA Attempts, only used in noninteractive modes")
|
rootCmd.PersistentFlags().Duration("mfaDelay", time.Second*10, "Delay between MFA Attempts, only used in noninteractive modes")
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().Bool("tlsSkipVerify", false, "Allow servers with self-signed certificates")
|
||||||
|
rootCmd.PersistentFlags().String("tlsClientPrivateKeyFile", "", "Client private key path for mtls")
|
||||||
|
rootCmd.PersistentFlags().String("tlsClientCertFile", "", "Client certificate path for mtls")
|
||||||
|
rootCmd.PersistentFlags().String("tlsClientPrivateKey", "", "Client private key for mtls")
|
||||||
|
rootCmd.PersistentFlags().String("tlsClientCert", "", "Client certificate for mtls")
|
||||||
|
|
||||||
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
|
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
|
||||||
viper.BindPFlag("timeout", rootCmd.PersistentFlags().Lookup("timeout"))
|
viper.BindPFlag("timeout", rootCmd.PersistentFlags().Lookup("timeout"))
|
||||||
viper.BindPFlag("serverAddress", rootCmd.PersistentFlags().Lookup("serverAddress"))
|
viper.BindPFlag("serverAddress", rootCmd.PersistentFlags().Lookup("serverAddress"))
|
||||||
|
@ -60,9 +72,27 @@ func init() {
|
||||||
viper.BindPFlag("userPassword", rootCmd.PersistentFlags().Lookup("userPassword"))
|
viper.BindPFlag("userPassword", rootCmd.PersistentFlags().Lookup("userPassword"))
|
||||||
viper.BindPFlag("mfaMode", rootCmd.PersistentFlags().Lookup("mfaMode"))
|
viper.BindPFlag("mfaMode", rootCmd.PersistentFlags().Lookup("mfaMode"))
|
||||||
viper.BindPFlag("totpToken", rootCmd.PersistentFlags().Lookup("totpToken"))
|
viper.BindPFlag("totpToken", rootCmd.PersistentFlags().Lookup("totpToken"))
|
||||||
|
viper.BindPFlag("mfaTotpToken", rootCmd.PersistentFlags().Lookup("mfaTotpToken"))
|
||||||
viper.BindPFlag("totpOffset", rootCmd.PersistentFlags().Lookup("totpOffset"))
|
viper.BindPFlag("totpOffset", rootCmd.PersistentFlags().Lookup("totpOffset"))
|
||||||
|
viper.BindPFlag("mfaTotpOffset", rootCmd.PersistentFlags().Lookup("mfaTotpOffset"))
|
||||||
viper.BindPFlag("mfaRetrys", rootCmd.PersistentFlags().Lookup("mfaRetrys"))
|
viper.BindPFlag("mfaRetrys", rootCmd.PersistentFlags().Lookup("mfaRetrys"))
|
||||||
viper.BindPFlag("mfaDelay", rootCmd.PersistentFlags().Lookup("mfaDelay"))
|
viper.BindPFlag("mfaDelay", rootCmd.PersistentFlags().Lookup("mfaDelay"))
|
||||||
|
|
||||||
|
viper.BindPFlag("tlsSkipVerify", rootCmd.PersistentFlags().Lookup("tlsSkipVerify"))
|
||||||
|
viper.BindPFlag("tlsClientCert", rootCmd.PersistentFlags().Lookup("tlsClientCert"))
|
||||||
|
viper.BindPFlag("tlsClientPrivateKey", rootCmd.PersistentFlags().Lookup("tlsClientPrivateKey"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToContent(file, contentFlag string) {
|
||||||
|
if viper.GetBool("debug") {
|
||||||
|
fmt.Fprintln(os.Stderr, "Loading file:", file)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error Loading File: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
viper.Set(contentFlag, string(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
// initConfig reads in config file and ENV variables if set.
|
||||||
|
@ -98,16 +128,32 @@ func initConfig() {
|
||||||
// Read in Private Key from File if userprivatekeyfile is set
|
// Read in Private Key from File if userprivatekeyfile is set
|
||||||
userprivatekeyfile, err := rootCmd.PersistentFlags().GetString("userPrivateKeyFile")
|
userprivatekeyfile, err := rootCmd.PersistentFlags().GetString("userPrivateKeyFile")
|
||||||
if err == nil && userprivatekeyfile != "" {
|
if err == nil && userprivatekeyfile != "" {
|
||||||
if viper.GetBool("debug") {
|
fileToContent(userprivatekeyfile, "userPrivateKey")
|
||||||
fmt.Fprintln(os.Stderr, "Loading Private Key from File:", userprivatekeyfile)
|
|
||||||
}
|
|
||||||
content, err := ioutil.ReadFile(userprivatekeyfile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error Loading Private Key from File: ", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
viper.Set("userprivatekey", string(content))
|
|
||||||
} else if err != nil && viper.GetBool("debug") {
|
} else if err != nil && viper.GetBool("debug") {
|
||||||
fmt.Fprintln(os.Stderr, "Getting Private Key File Flag:", err)
|
fmt.Fprintln(os.Stderr, "Getting Private Key File Flag:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read in Client Certificate Private Key from File if tlsClientPrivateKeyFile is set
|
||||||
|
tlsclientprivatekeyfile, err := rootCmd.PersistentFlags().GetString("tlsClientPrivateKeyFile")
|
||||||
|
if err == nil && tlsclientprivatekeyfile != "" {
|
||||||
|
fileToContent(tlsclientprivatekeyfile, "tlsClientPrivateKey")
|
||||||
|
} else if err != nil && viper.GetBool("debug") {
|
||||||
|
fmt.Fprintln(os.Stderr, "Getting Client Certificate Private key File Flag:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in Client Certificate from File if tlsClientCertFile is set
|
||||||
|
tlsclientcertfile, err := rootCmd.PersistentFlags().GetString("tlsClientCertFile")
|
||||||
|
if err == nil && tlsclientcertfile != "" {
|
||||||
|
fileToContent(tlsclientcertfile, "tlsClientCert")
|
||||||
|
} else if err != nil && viper.GetBool("debug") {
|
||||||
|
fmt.Fprintln(os.Stderr, "Getting Client Certificate File Flag:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetVersionInfo(version, commit, date string, dirty bool) {
|
||||||
|
v := fmt.Sprintf("%s (Built on %s from Git SHA %s)", version, date, commit)
|
||||||
|
if dirty {
|
||||||
|
v = v + " dirty"
|
||||||
|
}
|
||||||
|
rootCmd.Version = v
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/speatzle/go-passbolt-cli/folder"
|
"github.com/passbolt/go-passbolt-cli/folder"
|
||||||
"github.com/speatzle/go-passbolt-cli/group"
|
"github.com/passbolt/go-passbolt-cli/group"
|
||||||
"github.com/speatzle/go-passbolt-cli/resource"
|
"github.com/passbolt/go-passbolt-cli/resource"
|
||||||
"github.com/speatzle/go-passbolt-cli/user"
|
"github.com/passbolt/go-passbolt-cli/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// verifyCMD represents the verify command
|
// verifyCMD represents the verify command
|
||||||
|
@ -34,17 +32,20 @@ var verifyCMD = &cobra.Command{
|
||||||
|
|
||||||
userPassword := viper.GetString("userPassword")
|
userPassword := viper.GetString("userPassword")
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
fmt.Print("Enter Password:")
|
pw, err := util.ReadPassword("Enter Password:")
|
||||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return fmt.Errorf("Reading Password: %w", err)
|
return fmt.Errorf("Reading Password: %w", err)
|
||||||
}
|
}
|
||||||
userPassword = string(bytepw)
|
userPassword = pw
|
||||||
fmt.Println()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("Creating Client: %w", err)
|
return fmt.Errorf("Creating Client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package folder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,6 +34,10 @@ func FolderCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -53,6 +58,18 @@ func FolderCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating Folder: %w", err)
|
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)
|
fmt.Printf("FolderID: %v\n", id)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +29,10 @@ func FolderGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -39,15 +43,22 @@ func FolderGet(cmd *cobra.Command, args []string) error {
|
||||||
defer client.Logout(context.TODO())
|
defer client.Logout(context.TODO())
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
folderParentID, name, err := helper.GetFolder(
|
folder, err := client.GetFolder(ctx, id, nil)
|
||||||
ctx,
|
|
||||||
client,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting Folder: %w", err)
|
return fmt.Errorf("Getting Folder: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("FolderParentID: %v\n", folderParentID)
|
if jsonOutput {
|
||||||
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
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
|
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,12 +2,14 @@ package folder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
@ -26,7 +28,7 @@ func init() {
|
||||||
FolderListCmd.Flags().StringP("search", "s", "", "Folders that have this in the Name")
|
FolderListCmd.Flags().StringP("search", "s", "", "Folders that have this in the Name")
|
||||||
FolderListCmd.Flags().StringArrayP("folder", "f", []string{}, "Folders that are in this Folder")
|
FolderListCmd.Flags().StringArrayP("folder", "f", []string{}, "Folders that are in this Folder")
|
||||||
FolderListCmd.Flags().StringArrayP("group", "g", []string{}, "Folders that are shared with group")
|
FolderListCmd.Flags().StringArrayP("group", "g", []string{}, "Folders that are shared with group")
|
||||||
FolderListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name"}, "Columns to return, possible Columns:\nID, FolderParentID, Name")
|
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 {
|
func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -45,6 +47,14 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to Specify atleast one column to return")
|
return fmt.Errorf("You need to Specify atleast one column to return")
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
celFilter, err := cmd.Flags().GetString("filter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
@ -63,6 +73,28 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing Folder: %w", err)
|
return fmt.Errorf("Listing Folder: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folders, err = filterFolders(&folders, celFilter, ctx)
|
||||||
|
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}
|
data := pterm.TableData{columns}
|
||||||
|
|
||||||
for _, folder := range folders {
|
for _, folder := range folders {
|
||||||
|
@ -75,6 +107,10 @@ func FolderList(cmd *cobra.Command, args []string) error {
|
||||||
entry[i] = folder.FolderParentID
|
entry[i] = folder.FolderParentID
|
||||||
case "name":
|
case "name":
|
||||||
entry[i] = shellescape.StripUnsafe(folder.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:
|
default:
|
||||||
cmd.SilenceUsage = false
|
cmd.SilenceUsage = false
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
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()
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
75
go.mod
75
go.mod
|
@ -1,18 +1,67 @@
|
||||||
module github.com/speatzle/go-passbolt-cli
|
module github.com/passbolt/go-passbolt-cli
|
||||||
|
|
||||||
go 1.16
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.3.1 // indirect
|
al.essio.dev/pkg/shellescape v1.5.1
|
||||||
github.com/alessio/shellescape v1.4.1
|
github.com/google/cel-go v0.24.1
|
||||||
github.com/gookit/color v1.5.0 // indirect
|
github.com/passbolt/go-passbolt v0.7.2
|
||||||
github.com/pterm/pterm v0.12.33
|
github.com/pterm/pterm v0.12.80
|
||||||
github.com/speatzle/go-passbolt v0.5.2
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/viper v1.19.0
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/tobischo/gokeepasslib/v3 v3.6.1
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
golang.org/x/term v0.29.0
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// replace github.com/speatzle/go-passbolt => ../go-passbolt
|
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,10 +2,11 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,6 +41,10 @@ func GroupCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ops := []helper.GroupMembershipOperation{}
|
ops := []helper.GroupMembershipOperation{}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
|
@ -74,6 +79,18 @@ func GroupCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating Group: %w", err)
|
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)
|
fmt.Printf("GroupID: %v\n", id)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
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
|
||||||
|
}
|
37
group/get.go
37
group/get.go
|
@ -2,13 +2,14 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"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"
|
"github.com/pterm/pterm"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ var GroupGetCmd = &cobra.Command{
|
||||||
func init() {
|
func init() {
|
||||||
GroupGetCmd.Flags().String("id", "", "id of Group to Get")
|
GroupGetCmd.Flags().String("id", "", "id of Group to Get")
|
||||||
|
|
||||||
GroupGetCmd.Flags().StringArrayP("column", "c", []string{"UserID", "IsGroupManager"}, "Membership Columns to return, possible Columns:\nUserID, Username, UserFirstName, UserLastName, IsGroupManager")
|
GroupGetCmd.Flags().StringArrayP("column", "c", []string{"UserID", "Username", "UserFirstName", "UserLastName", "IsGroupManager"}, "Membership Columns to return, possible Columns:\nUserID, Username, UserFirstName, UserLastName, IsGroupManager")
|
||||||
|
|
||||||
GroupGetCmd.MarkFlagRequired("id")
|
GroupGetCmd.MarkFlagRequired("id")
|
||||||
}
|
}
|
||||||
|
@ -37,6 +38,10 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -55,6 +60,29 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting Group: %w", err)
|
return fmt.Errorf("Getting Group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
fmt.Printf("Name: %v\n", name)
|
||||||
// Print Memberships
|
// Print Memberships
|
||||||
if len(columns) != 0 {
|
if len(columns) != 0 {
|
||||||
|
@ -84,5 +112,6 @@ func GroupGet(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
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,12 +2,14 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
@ -26,7 +28,7 @@ func init() {
|
||||||
GroupListCmd.Flags().StringArrayP("user", "u", []string{}, "Groups that are shared with group")
|
GroupListCmd.Flags().StringArrayP("user", "u", []string{}, "Groups that are shared with group")
|
||||||
GroupListCmd.Flags().StringArrayP("manager", "m", []string{}, "Groups that are in folder")
|
GroupListCmd.Flags().StringArrayP("manager", "m", []string{}, "Groups that are in folder")
|
||||||
|
|
||||||
GroupListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Name"}, "Columns to return, possible Columns:\nID, Name")
|
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 {
|
func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -45,6 +47,14 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
celFilter, err := cmd.Flags().GetString("filter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -55,7 +65,7 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
defer client.Logout(context.TODO())
|
defer client.Logout(context.TODO())
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
resources, err := client.GetGroups(ctx, &api.GetGroupsOptions{
|
groups, err := client.GetGroups(ctx, &api.GetGroupsOptions{
|
||||||
FilterHasUsers: users,
|
FilterHasUsers: users,
|
||||||
FilterHasManagers: managers,
|
FilterHasManagers: managers,
|
||||||
})
|
})
|
||||||
|
@ -63,16 +73,41 @@ func GroupList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing Group: %w", err)
|
return fmt.Errorf("Listing Group: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groups, err = filterGroups(&groups, celFilter, ctx)
|
||||||
|
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}
|
data := pterm.TableData{columns}
|
||||||
|
|
||||||
for _, resource := range resources {
|
for _, group := range groups {
|
||||||
entry := make([]string, len(columns))
|
entry := make([]string, len(columns))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
switch strings.ToLower(columns[i]) {
|
switch strings.ToLower(columns[i]) {
|
||||||
case "id":
|
case "id":
|
||||||
entry[i] = resource.ID
|
entry[i] = group.ID
|
||||||
case "name":
|
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:
|
default:
|
||||||
cmd.SilenceUsage = false
|
cmd.SilenceUsage = false
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
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()
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
229
keepass/export.go
Normal file
229
keepass/export.go
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
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()
|
||||||
|
}
|
5
main.go
5
main.go
|
@ -1,7 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/speatzle/go-passbolt-cli/cmd"
|
import (
|
||||||
|
"github.com/passbolt/go-passbolt-cli/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
cmd.SetVersionInfo(version, commit, date, dirty)
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,6 +55,10 @@ func ResourceCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -78,6 +83,18 @@ func ResourceCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating Resource: %w", err)
|
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)
|
fmt.Printf("ResourceID: %v\n", id)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
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
|
||||||
|
}
|
132
resource/get.go
132
resource/get.go
|
@ -2,11 +2,16 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
|
"github.com/pterm/pterm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,10 +23,24 @@ var ResourceGetCmd = &cobra.Command{
|
||||||
RunE: ResourceGet,
|
RunE: ResourceGet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourcePermissionCmd Gets Permissions for Passbolt Resource
|
||||||
|
var ResourcePermissionCmd = &cobra.Command{
|
||||||
|
Use: "permission",
|
||||||
|
Short: "Gets Permissions for a Passbolt Resource",
|
||||||
|
Long: `Gets Permissions for a Passbolt Resource`,
|
||||||
|
Aliases: []string{"permissions"},
|
||||||
|
RunE: ResourcePermission,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ResourceGetCmd.Flags().String("id", "", "id of Resource to Get")
|
ResourceGetCmd.Flags().String("id", "", "id of Resource to Get")
|
||||||
|
|
||||||
ResourceGetCmd.MarkFlagRequired("id")
|
ResourceGetCmd.MarkFlagRequired("id")
|
||||||
|
|
||||||
|
ResourceGetCmd.AddCommand(ResourcePermissionCmd)
|
||||||
|
ResourcePermissionCmd.Flags().String("id", "", "id of Resource to Get")
|
||||||
|
ResourcePermissionCmd.Flags().StringArrayP("column", "c", []string{"ID", "Aco", "AcoForeignKey", "Aro", "AroForeignKey", "Type"}, "Columns to return, possible Columns:\nID, Aco, AcoForeignKey, Aro, AroForeignKey, Type, CreatedTimestamp, ModifiedTimestamp")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResourceGet(cmd *cobra.Command, args []string) error {
|
func ResourceGet(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -29,6 +48,10 @@ func ResourceGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -47,11 +70,114 @@ func ResourceGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting Resource: %w", err)
|
return fmt.Errorf("Getting Resource: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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("FolderParentID: %v\n", folderParentID)
|
||||||
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
fmt.Printf("Name: %v\n", shellescape.StripUnsafe(name))
|
||||||
fmt.Printf("Username: %v\n", shellescape.StripUnsafe(username))
|
fmt.Printf("Username: %v\n", shellescape.StripUnsafe(username))
|
||||||
fmt.Printf("URI: %v\n", shellescape.StripUnsafe(uri))
|
fmt.Printf("URI: %v\n", shellescape.StripUnsafe(uri))
|
||||||
fmt.Printf("Password: %v\n", shellescape.StripUnsafe(password))
|
fmt.Printf("Password: %v\n", shellescape.StripUnsafe(password))
|
||||||
fmt.Printf("Description: %v\n", shellescape.StripUnsafe(description))
|
fmt.Printf("Description: %v\n", shellescape.StripUnsafe(description))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResourcePermission(cmd *cobra.Command, args []string) error {
|
||||||
|
resource, err := cmd.Flags().GetString("id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
columns, err := cmd.Flags().GetStringArray("column")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(columns) == 0 {
|
||||||
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
client, err := util.GetClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Logout(context.TODO())
|
||||||
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
|
permissions, err := client.GetResourcePermissions(ctx, resource)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Listing Permission: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
outputPermissions := []PermissionJsonOutput{}
|
||||||
|
for i := range permissions {
|
||||||
|
outputPermissions = append(outputPermissions, PermissionJsonOutput{
|
||||||
|
ID: &permissions[i].ID,
|
||||||
|
Aco: &permissions[i].ACO,
|
||||||
|
AcoForeignKey: &permissions[i].ACOForeignKey,
|
||||||
|
Aro: &permissions[i].ARO,
|
||||||
|
AroForeignKey: &permissions[i].AROForeignKey,
|
||||||
|
Type: &permissions[i].Type,
|
||||||
|
CreatedTimestamp: &permissions[i].Created.Time,
|
||||||
|
ModifiedTimestamp: &permissions[i].Modified.Time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
jsonPermissions, err := json.MarshalIndent(outputPermissions, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(jsonPermissions))
|
||||||
|
} else {
|
||||||
|
data := pterm.TableData{columns}
|
||||||
|
|
||||||
|
for _, permission := range permissions {
|
||||||
|
entry := make([]string, len(columns))
|
||||||
|
for i := range columns {
|
||||||
|
switch strings.ToLower(columns[i]) {
|
||||||
|
case "id":
|
||||||
|
entry[i] = permission.ID
|
||||||
|
case "aco":
|
||||||
|
entry[i] = permission.ACO
|
||||||
|
case "acoforeignkey":
|
||||||
|
entry[i] = permission.ACOForeignKey
|
||||||
|
case "aro":
|
||||||
|
entry[i] = permission.ARO
|
||||||
|
case "aroforeignkey":
|
||||||
|
entry[i] = permission.AROForeignKey
|
||||||
|
case "type":
|
||||||
|
entry[i] = strconv.Itoa(permission.Type)
|
||||||
|
case "createdtimestamp":
|
||||||
|
entry[i] = permission.Created.Format(time.RFC3339)
|
||||||
|
case "modifiedtimestamp":
|
||||||
|
entry[i] = permission.Modified.Format(time.RFC3339)
|
||||||
|
default:
|
||||||
|
cmd.SilenceUsage = false
|
||||||
|
return fmt.Errorf("Unknown Column: %v", columns[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = append(data, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
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,13 +2,15 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
@ -26,11 +28,9 @@ var ResourceListCmd = &cobra.Command{
|
||||||
func init() {
|
func init() {
|
||||||
ResourceListCmd.Flags().Bool("favorite", false, "Resources that are marked as favorite")
|
ResourceListCmd.Flags().Bool("favorite", false, "Resources that are marked as favorite")
|
||||||
ResourceListCmd.Flags().Bool("own", false, "Resources that are owned by me")
|
ResourceListCmd.Flags().Bool("own", false, "Resources that are owned by me")
|
||||||
|
ResourceListCmd.Flags().StringP("group", "g", "", "Resources that are shared with group")
|
||||||
ResourceListCmd.Flags().StringArrayP("group", "g", []string{}, "Resources that are shared with group")
|
|
||||||
ResourceListCmd.Flags().StringArrayP("folder", "f", []string{}, "Resources that are in folder")
|
ResourceListCmd.Flags().StringArrayP("folder", "f", []string{}, "Resources that are in folder")
|
||||||
|
ResourceListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name", "Username", "URI"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, Username, URI, Password, Description, CreatedTimestamp, ModifiedTimestamp")
|
||||||
ResourceListCmd.Flags().StringArrayP("column", "c", []string{"ID", "FolderParentID", "Name", "Username", "URI"}, "Columns to return, possible Columns:\nID, FolderParentID, Name, Username, URI, Password, Description")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResourceList(cmd *cobra.Command, args []string) error {
|
func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -42,7 +42,7 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
groups, err := cmd.Flags().GetStringArray("group")
|
group, err := cmd.Flags().GetString("group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,14 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
celFilter, err := cmd.Flags().GetString("filter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -70,13 +78,43 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
resources, err := client.GetResources(ctx, &api.GetResourcesOptions{
|
resources, err := client.GetResources(ctx, &api.GetResourcesOptions{
|
||||||
FilterIsFavorite: favorite,
|
FilterIsFavorite: favorite,
|
||||||
FilterIsOwnedByMe: own,
|
FilterIsOwnedByMe: own,
|
||||||
FilterIsSharedWithGroup: groups,
|
FilterIsSharedWithGroup: group,
|
||||||
FilterHasParent: folderParents,
|
FilterHasParent: folderParents,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Listing Resource: %w", err)
|
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}
|
data := pterm.TableData{columns}
|
||||||
|
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
|
@ -105,6 +143,10 @@ func ResourceList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Get Resource %w", err)
|
return fmt.Errorf("Get Resource %w", err)
|
||||||
}
|
}
|
||||||
entry[i] = shellescape.StripUnsafe(desc)
|
entry[i] = shellescape.StripUnsafe(desc)
|
||||||
|
case "createdtimestamp":
|
||||||
|
entry[i] = resource.Created.Format(time.RFC3339)
|
||||||
|
case "modifiedtimestamp":
|
||||||
|
entry[i] = resource.Modified.Format(time.RFC3339)
|
||||||
default:
|
default:
|
||||||
cmd.SilenceUsage = false
|
cmd.SilenceUsage = false
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
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()
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,11 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,6 +46,11 @@ func UserCreate(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
client, err := util.GetClient(ctx)
|
client, err := util.GetClient(ctx)
|
||||||
|
@ -66,6 +72,18 @@ func UserCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Creating User: %w", err)
|
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)
|
fmt.Printf("UserID: %v\n", id)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
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
|
||||||
|
}
|
25
user/get.go
25
user/get.go
|
@ -2,11 +2,12 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +30,10 @@ func UserGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -47,10 +52,22 @@ func UserGet(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting User: %w", err)
|
return fmt.Errorf("Getting User: %w", err)
|
||||||
}
|
}
|
||||||
|
if jsonOutput {
|
||||||
|
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("Username: %v\n", shellescape.StripUnsafe(username))
|
||||||
fmt.Printf("FirstName: %v\n", shellescape.StripUnsafe(firstname))
|
fmt.Printf("FirstName: %v\n", shellescape.StripUnsafe(firstname))
|
||||||
fmt.Printf("LastName: %v\n", shellescape.StripUnsafe(lastname))
|
fmt.Printf("LastName: %v\n", shellescape.StripUnsafe(lastname))
|
||||||
fmt.Printf("Role: %v\n", shellescape.StripUnsafe(role))
|
fmt.Printf("Role: %v\n", shellescape.StripUnsafe(role))
|
||||||
|
}
|
||||||
return nil
|
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"`
|
||||||
|
}
|
47
user/list.go
47
user/list.go
|
@ -2,12 +2,14 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"al.essio.dev/pkg/shellescape"
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
@ -29,7 +31,7 @@ func init() {
|
||||||
UserListCmd.Flags().StringP("search", "s", "", "Search for Users")
|
UserListCmd.Flags().StringP("search", "s", "", "Search for Users")
|
||||||
UserListCmd.Flags().BoolP("admin", "a", false, "Only show Admins")
|
UserListCmd.Flags().BoolP("admin", "a", false, "Only show Admins")
|
||||||
|
|
||||||
UserListCmd.Flags().StringArrayP("column", "c", []string{"ID", "Username", "FirstName", "LastName", "Role"}, "Columns to return, possible Columns:\nID, Username, FirstName, LastName, Role")
|
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 {
|
func UserList(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -56,6 +58,14 @@ func UserList(cmd *cobra.Command, args []string) error {
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
return fmt.Errorf("You need to specify atleast one column to return")
|
return fmt.Errorf("You need to specify atleast one column to return")
|
||||||
}
|
}
|
||||||
|
jsonOutput, err := cmd.Flags().GetBool("json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
celFilter, err := cmd.Flags().GetString("filter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := util.GetContext()
|
ctx := util.GetContext()
|
||||||
|
|
||||||
|
@ -76,6 +86,30 @@ func UserList(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("Listing User: %w", err)
|
return fmt.Errorf("Listing User: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
users, err = filterUsers(&users, celFilter, ctx)
|
||||||
|
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}
|
data := pterm.TableData{columns}
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
|
@ -92,6 +126,10 @@ func UserList(cmd *cobra.Command, args []string) error {
|
||||||
entry[i] = shellescape.StripUnsafe(user.Profile.LastName)
|
entry[i] = shellescape.StripUnsafe(user.Profile.LastName)
|
||||||
case "role":
|
case "role":
|
||||||
entry[i] = shellescape.StripUnsafe(user.Role.Name)
|
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:
|
default:
|
||||||
cmd.SilenceUsage = false
|
cmd.SilenceUsage = false
|
||||||
return fmt.Errorf("Unknown Column: %v", columns[i])
|
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()
|
pterm.DefaultTable.WithHasHeader().WithData(data).Render()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
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,19 +1,46 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"syscall"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/speatzle/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/speatzle/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ReadPassword reads a Password interactively or via Pipe
|
||||||
|
func ReadPassword(prompt string) (string, error) {
|
||||||
|
fd := int(os.Stdin.Fd())
|
||||||
|
var pass string
|
||||||
|
if term.IsTerminal(fd) {
|
||||||
|
fmt.Fprint(os.Stderr, prompt);
|
||||||
|
|
||||||
|
inputPass, err := term.ReadPassword(fd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pass = string(inputPass)
|
||||||
|
} else {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
s, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pass = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(pass, "\n", "", 1), nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetClient gets a Logged in Passbolt Client
|
// GetClient gets a Logged in Passbolt Client
|
||||||
func GetClient(ctx context.Context) (*api.Client, error) {
|
func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
serverAddress := viper.GetString("serverAddress")
|
serverAddress := viper.GetString("serverAddress")
|
||||||
|
@ -28,17 +55,21 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
|
|
||||||
userPassword := viper.GetString("userPassword")
|
userPassword := viper.GetString("userPassword")
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
fmt.Print("Enter Password:")
|
cliPassword, err := ReadPassword("Enter Password:")
|
||||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return nil, fmt.Errorf("Reading Password: %w", err)
|
return nil, fmt.Errorf("Reading Password: %w", err)
|
||||||
}
|
}
|
||||||
userPassword = string(bytepw)
|
|
||||||
|
userPassword = cliPassword
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Creating Client: %w", err)
|
return nil, fmt.Errorf("Creating Client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -58,32 +89,30 @@ func GetClient(ctx context.Context) (*api.Client, error) {
|
||||||
switch viper.GetString("mfaMode") {
|
switch viper.GetString("mfaMode") {
|
||||||
case "interactive-totp":
|
case "interactive-totp":
|
||||||
client.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
|
client.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
|
||||||
challange := api.MFAChallange{}
|
challenge := api.MFAChallenge{}
|
||||||
err := json.Unmarshal(res.Body, &challange)
|
err := json.Unmarshal(res.Body, &challenge)
|
||||||
if err != nil {
|
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")
|
return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider")
|
||||||
}
|
}
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
var code string
|
var code string
|
||||||
fmt.Print("Enter TOTP:")
|
code, err := ReadPassword("Enter TOTP:")
|
||||||
bytepw, err := term.ReadPassword(int(syscall.Stdin))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
|
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
|
||||||
}
|
}
|
||||||
code = string(bytepw)
|
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
req := api.MFAChallangeResponse{
|
req := api.MFAChallengeResponse{
|
||||||
TOTP: code,
|
TOTP: code,
|
||||||
}
|
}
|
||||||
var raw *http.Response
|
var raw *http.Response
|
||||||
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
|
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
|
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
|
||||||
return http.Cookie{}, fmt.Errorf("Doing MFA Challange Response: %w", err)
|
return http.Cookie{}, fmt.Errorf("Doing MFA Challenge Response: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("TOTP Verification Failed")
|
fmt.Println("TOTP Verification Failed")
|
||||||
} else {
|
} 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("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":
|
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":
|
case "none":
|
||||||
default:
|
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