diff --git a/Gopkg.lock b/Gopkg.lock index 918eb6e..302e053 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,15 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/mattn/go-zglob" + packages = [ + ".", + "fastwalk" + ] + revision = "4959821b481786922ac53e7ef25c61ae19fb7c36" + [[projects]] name = "github.com/rifflock/lfshook" packages = ["."] @@ -34,6 +43,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f6199a143c790c5c1422bd35477ede08e5d2e3a0e5552d769e7c4de6ed8a14b1" + inputs-digest = "1f59ddaed2db7de2b34f8485550888fa1cb625342398af10acb68e5ee227f3f8" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index addef28..87d0aea 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,3 +27,7 @@ [[constraint]] name = "github.com/rifflock/lfshook" version = "2.3.0" + +[[constraint]] + branch = "master" + name = "github.com/mattn/go-zglob" diff --git a/PROTOCOL.md b/PROTOCOL.md index c06e7ec..c4f8835 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -41,16 +41,18 @@ should be supplied as a `message` parameter. ## List of Error Codes -| Code | Description | Parameters | -| ---- | ----------------------------------------------------------------------- | ----------------- | -| 10 | Unable to parse browser request length | error | -| 11 | Unable to parse browser request | error | -| 12 | Invalid request action | action | -| 13 | Inaccessible user-configured password store | error, name, path | -| 14 | Inaccessible default password store | error, path | -| 15 | Unable to determine the location of the default password store | error | -| 16 | Unable to read the default settings of a user-configured password store | error, name, path | -| 17 | Unable to read the default settings of the default password store | error, path | +| Code | Description | Parameters | +| ---- | ----------------------------------------------------------------------- | ----------------------- | +| 10 | Unable to parse browser request length | error | +| 11 | Unable to parse browser request | error | +| 12 | Invalid request action | action | +| 13 | Inaccessible user-configured password store | error, name, path | +| 14 | Inaccessible default password store | error, path | +| 15 | Unable to determine the location of the default password store | error | +| 16 | Unable to read the default settings of a user-configured password store | error, name, path | +| 17 | Unable to read the default settings of the default password store | error, path | +| 18 | Unable to list files in a password store | error, name, path | +| 19 | Unable to determine a relative path for a file in a password store | error, file, name, path | ## Settings @@ -171,7 +173,7 @@ Get the decrypted contents of a specific file. "status": "ok", "version": , "data": { - "data": "" + "contents": "" } } ``` diff --git a/errors/errors.go b/errors/errors.go index cca95a8..34a375f 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -33,6 +33,12 @@ const ( // CodeUnreadableDefaultPasswordStoreDefaultSettings error reading the default settings of the default password store CodeUnreadableDefaultPasswordStoreDefaultSettings = 17 + + // CodeUnableToListFilesInPasswordStore error listing files in a password store + CodeUnableToListFilesInPasswordStore = 18 + + // CodeUnableToDetermineRelativeFilePathInPasswordStore error determining a relative path for a file in a password store + CodeUnableToDetermineRelativeFilePathInPasswordStore = 19 ) // ExitWithCode exit with error code diff --git a/placeholder.sh b/placeholder.sh index ecc8e84..e1979fe 100755 --- a/placeholder.sh +++ b/placeholder.sh @@ -16,7 +16,7 @@ function require() if ! `command -v "$1" >/dev/null`; then OUTPUT="{\n \"status\": \"error\"\n "version": $VERSION,\n \"params\": {\n \"message\": \"Required dependency '$1' is missing\"\n }, \"code\": 1\n}" LANG=C LC_ALL=C LENGTH=${#OUTPUT} - echo -n + echo -n exit 1 fi } @@ -94,11 +94,11 @@ function configure() done [ -n "$PASSWORD_STORE_DIR" ] || PASSWORD_STORE_DIR="~/.password-store" - OUTPUT="$(jq -n --arg defaultPath "$PASSWORD_STORE_DIR" '.defaultPath = $defaultPath')" + OUTPUT="$(jq -n --arg defaultPath "$PASSWORD_STORE_DIR" '.defaultStore.path = $defaultPath')" STOREPATH=$(echo "$PASSWORD_STORE_DIR" | sed 's/^~/$HOME/' | envsubst) if [ -f "$STOREPATH/.browserpass.json" ]; then - OUTPUT=$(jq --arg settings "$(cat "$STOREPATH/.browserpass.json")" '.defaultSettings = $settings' <<< "$OUTPUT") + OUTPUT=$(jq --arg settings "$(cat "$STOREPATH/.browserpass.json")" '.defaultStore.settings = $settings' <<< "$OUTPUT") else OUTPUT=$(jq '.defaultSettings = ""' <<< "$OUTPUT") fi @@ -153,9 +153,9 @@ function fetch() # get file contents [ -f "$FILEPATH" ] || fail "Requested file does not exist: $STORE:$FILE" DATA="$(gpg -q --decrypt "$FILEPATH")" - + # build output - echo "$(jq -n --arg data "$DATA" '.data = $data')" + echo "$(jq -n --arg data "$DATA" '.contents = $data')" } function run() diff --git a/request/list.go b/request/list.go new file mode 100644 index 0000000..bd90cbd --- /dev/null +++ b/request/list.go @@ -0,0 +1,69 @@ +package request + +import ( + "path/filepath" + "sort" + + "github.com/browserpass/browserpass-native/errors" + "github.com/browserpass/browserpass-native/response" + "github.com/mattn/go-zglob" + log "github.com/sirupsen/logrus" +) + +func listFiles(request request) { + responseData := response.MakeListResponse() + + for _, store := range request.Settings.Stores { + normalizedStorePath, err := normalizePasswordStorePath(store.Path) + if err != nil { + log.Errorf( + "The password store '%v' is not accessible at the location '%v': %+v", + store.Name, store.Path, err, + ) + response.SendError( + errors.CodeInaccessiblePasswordStore, + "The password store is not accessible", + &map[string]string{"error": err.Error(), "name": store.Name, "path": store.Path}, + ) + errors.ExitWithCode(errors.CodeInaccessiblePasswordStore) + } + + store.Path = normalizedStorePath + + files, err := zglob.GlobFollowSymlinks(filepath.Join(store.Path, "/**/*.gpg")) + if err != nil { + log.Errorf( + "Unable to list the files in the password store '%v' at the location '%v': %+v", + store.Name, store.Path, err, + ) + response.SendError( + errors.CodeUnableToListFilesInPasswordStore, + "Unable to list the files in the password store", + &map[string]string{"error": err.Error(), "name": store.Name, "path": store.Path}, + ) + errors.ExitWithCode(errors.CodeUnableToListFilesInPasswordStore) + } + + for i, file := range files { + relativePath, err := filepath.Rel(store.Path, file) + if err != nil { + log.Errorf( + "Unable to determine the relative path for a file '%v' in the password store '%v' at the location '%v': %+v", + file, store.Name, store.Path, err, + ) + response.SendError( + errors.CodeUnableToDetermineRelativeFilePathInPasswordStore, + "Unable to determine the relative path for a file in the password store", + &map[string]string{"error": err.Error(), "file": file, "name": store.Name, "path": store.Path}, + ) + errors.ExitWithCode(errors.CodeUnableToDetermineRelativeFilePathInPasswordStore) + } + files[i] = relativePath + } + + sort.Strings(files) + responseData.Files[store.Name] = files + } + + response.SendOk(responseData) +} diff --git a/request/process.go b/request/process.go index d2f712f..d4b99b4 100644 --- a/request/process.go +++ b/request/process.go @@ -31,7 +31,7 @@ func Process() { case "configure": configure(request) case "list": - break + listFiles(request) case "fetch": break default: diff --git a/response/response.go b/response/response.go index ac12473..230de23 100644 --- a/response/response.go +++ b/response/response.go @@ -40,6 +40,18 @@ func MakeConfigureResponse() *ConfigureResponse { } } +// ListResponse a response format for the "list" request +type ListResponse struct { + Files map[string][]string `json:"files"` +} + +// MakeListResponse initializes an empty list response +func MakeListResponse() *ListResponse { + return &ListResponse{ + Files: make(map[string][]string), + } +} + // SendOk sends a success response to the browser extension in the predefined json format func SendOk(data interface{}) { send(&okResponse{