Implement configure action, always send external error in parameters (#11)

This commit is contained in:
Maxim Baz
2018-04-16 00:46:53 +02:00
committed by GitHub
parent 42e6157238
commit 66850cfa4a
6 changed files with 234 additions and 15 deletions

View File

@@ -41,11 +41,16 @@ should be supplied as a `message` parameter.
## List of Error Codes
| Code | Description | Parameters |
| ---- | -------------------------------------- | ---------- |
| 10 | Unable to parse browser request length | |
| 11 | Unable to parse browser request | |
| 12 | Invalid request action | action |
| 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 |
## Settings
@@ -106,7 +111,7 @@ is alive, determine the version at startup, and provide per-store defaults.
"data": {
"defaultStore": {
"path": "/path/to/default/store",
"defaultSettings": "<raw contents of $defaultPath/.browserpass.json>",
"settings": "<raw contents of $defaultPath/.browserpass.json>",
},
“storeSettings”: {
“storeName”: "<raw contents of storePath/.browserpass.json>"

View File

@@ -10,14 +10,29 @@ type Code int
// Error codes that are sent to the browser extension and used as exit codes in the app.
// DO NOT MODIFY THE VALUES, always append new error codes to the bottom.
const (
// CodeParseRequestLength error parsing request length
// CodeParseRequestLength error parsing a request length
CodeParseRequestLength Code = 10
// CodeParseRequest error parsing request
// CodeParseRequest error parsing a request
CodeParseRequest Code = 11
// CodeInvalidRequestAction error parsing request action
// CodeInvalidRequestAction error parsing a request action
CodeInvalidRequestAction = 12
// CodeInaccessiblePasswordStore error accessing a user-configured password store
CodeInaccessiblePasswordStore = 13
// CodeInaccessibleDefaultPasswordStore error accessing the default password store
CodeInaccessibleDefaultPasswordStore = 14
// CodeUnknownDefaultPasswordStoreLocation error determining the location of the default password store
CodeUnknownDefaultPasswordStoreLocation = 15
// CodeUnreadablePasswordStoreDefaultSettings error reading the default settings of a user-configured password store
CodeUnreadablePasswordStoreDefaultSettings = 16
// CodeUnreadableDefaultPasswordStoreDefaultSettings error reading the default settings of the default password store
CodeUnreadableDefaultPasswordStoreDefaultSettings = 17
)
// ExitWithCode exit with error code

30
request/common.go Normal file
View File

@@ -0,0 +1,30 @@
package request
import (
"errors"
"os"
"path/filepath"
"strings"
)
func normalizePasswordStorePath(storePath string) (string, error) {
if strings.HasPrefix(storePath, "~/") {
storePath = filepath.Join("$HOME", storePath[2:])
}
storePath = os.ExpandEnv(storePath)
directStorePath, err := filepath.EvalSymlinks(storePath)
if err != nil {
return "", err
}
storePath = directStorePath
stat, err := os.Stat(storePath)
if err != nil {
return "", err
}
if !stat.IsDir() {
return "", errors.New("The specified path exists, but is not a directory")
}
return storePath, nil
}

126
request/configure.go Normal file
View File

@@ -0,0 +1,126 @@
package request
import (
"io/ioutil"
"os"
"os/user"
"path/filepath"
"github.com/browserpass/browserpass-native/errors"
"github.com/browserpass/browserpass-native/response"
log "github.com/sirupsen/logrus"
)
func configure(request request) {
responseData := response.MakeConfigureResponse()
// Check that each and every store in the settings exists and is accessible.
// Then read the default configuration for these stores (if available).
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
responseData.StoreSettings[store.Name], err = readDefaultSettings(store.Path)
if err != nil {
log.Errorf(
"Unable to read the default settings of the user-configured password store '%v' in '%v': %+v",
store.Name, store.Path, err,
)
response.SendError(
errors.CodeUnreadablePasswordStoreDefaultSettings,
"Unable to read the default settings of the password store",
&map[string]string{"error": err.Error(), "name": store.Name, "path": store.Path},
)
errors.ExitWithCode(errors.CodeUnreadablePasswordStoreDefaultSettings)
}
}
// Check whether a store in the default location exists and is accessible.
// If there is at least one store in the settings, it is expected that there might be no store in the default location => do not return errors.
// However, if there are no stores in the settings, user expects to use the default password store => return an error if it is not accessible.
possibleDefaultStorePath, err := getDefaultPasswordStorePath()
if err != nil {
if len(request.Settings.Stores) == 0 {
log.Error("Unable to determine the location of the default password store: ", err)
response.SendError(
errors.CodeUnknownDefaultPasswordStoreLocation,
"Unable to determine the location of the default password store",
&map[string]string{"error": err.Error()},
)
errors.ExitWithCode(errors.CodeUnknownDefaultPasswordStoreLocation)
}
} else {
responseData.DefaultStore.Path, err = normalizePasswordStorePath(possibleDefaultStorePath)
if err != nil {
if len(request.Settings.Stores) == 0 {
log.Errorf(
"The default password store is not accessible at the location '%v': %+v",
possibleDefaultStorePath, err,
)
response.SendError(
errors.CodeInaccessibleDefaultPasswordStore,
"The default password store is not accessible",
&map[string]string{"error": err.Error(), "path": possibleDefaultStorePath},
)
errors.ExitWithCode(errors.CodeInaccessibleDefaultPasswordStore)
}
}
}
if responseData.DefaultStore.Path != "" {
responseData.DefaultStore.Settings, err = readDefaultSettings(responseData.DefaultStore.Path)
if err != nil {
log.Errorf(
"Unable to read the default settings of the default password store in '%v': %+v",
responseData.DefaultStore.Path, err,
)
response.SendError(
errors.CodeUnreadableDefaultPasswordStoreDefaultSettings,
"Unable to read the default settings of the default password store",
&map[string]string{"error": err.Error(), "path": responseData.DefaultStore.Path},
)
errors.ExitWithCode(errors.CodeUnreadableDefaultPasswordStoreDefaultSettings)
}
}
response.SendOk(responseData)
}
func getDefaultPasswordStorePath() (string, error) {
path := os.Getenv("PASSWORD_STORE_DIR")
if path != "" {
return path, nil
}
usr, err := user.Current()
if err != nil {
return "", err
}
path = filepath.Join(usr.HomeDir, ".password-store")
return path, nil
}
func readDefaultSettings(storePath string) (string, error) {
content, err := ioutil.ReadFile(filepath.Join(storePath, ".browserpass.json"))
if err == nil {
return string(content), nil
}
if os.IsNotExist(err) {
return "", nil
}
return "", err
}

View File

@@ -12,8 +12,14 @@ import (
)
type request struct {
Action string `json:"action"`
Settings interface{} `json:"settings"`
Action string `json:"action"`
Settings struct {
GpgPath string `json:"gpgPath"`
Stores map[string]struct {
Name string `json:"name"`
Path string `json:"path"`
}
} `json:"settings"`
}
// Process handles browser request
@@ -23,14 +29,18 @@ func Process() {
switch request.Action {
case "configure":
break
configure(request)
case "list":
break
case "fetch":
break
default:
log.Errorf("Received a browser request with an unknown action: %+v", request)
response.SendError(errors.CodeInvalidRequestAction, "Invalid request action", &map[string]string{"action": request.Action})
response.SendError(
errors.CodeInvalidRequestAction,
"Invalid request action",
&map[string]string{"action": request.Action},
)
errors.ExitWithCode(errors.CodeInvalidRequestAction)
}
}
@@ -44,7 +54,11 @@ func parseRequestLength() uint32 {
// return
// }
log.Error("Unable to parse the length of the browser request: ", err)
response.SendError(errors.CodeParseRequestLength, "Unable to parse the length of the browser request", nil)
response.SendError(
errors.CodeParseRequestLength,
"Unable to parse the length of the browser request",
&map[string]string{"error": err.Error()},
)
errors.ExitWithCode(errors.CodeParseRequestLength)
}
return length
@@ -56,7 +70,11 @@ func parseRequest(messageLength uint32) request {
reader := &io.LimitedReader{R: os.Stdin, N: int64(messageLength)}
if err := json.NewDecoder(reader).Decode(&parsed); err != nil {
log.Error("Unable to parse the browser request: ", err)
response.SendError(errors.CodeParseRequest, "Unable to parse the browser request", nil)
response.SendError(
errors.CodeParseRequest,
"Unable to parse the browser request",
&map[string]string{"error": err.Error()},
)
errors.ExitWithCode(errors.CodeParseRequest)
}
return parsed

View File

@@ -24,6 +24,31 @@ type errorResponse struct {
Params interface{} `json:"params"`
}
// ConfigureResponse a response format for the "configure" request
type ConfigureResponse struct {
DefaultStore struct {
Path string `json:"path"`
Settings string `json:"settings"`
} `json:"defaultStore"`
StoreSettings map[string]string `json:"storeSettings"`
}
// MakeConfigureResponse initializes an empty configure response
func MakeConfigureResponse() *ConfigureResponse {
return &ConfigureResponse{
StoreSettings: make(map[string]string),
}
}
// SendOk sends a success response to the browser extension in the predefined json format
func SendOk(data interface{}) {
send(&okResponse{
Status: "ok",
Version: version.Code,
Data: data,
})
}
// SendError sends an error response to the browser extension in the predefined json format
func SendError(errorCode errors.Code, errorMsg string, extraParams *map[string]string) {
params := map[string]string{