Implement configure
action, always send external error in parameters (#11)
This commit is contained in:
17
PROTOCOL.md
17
PROTOCOL.md
@@ -41,11 +41,16 @@ should be supplied as a `message` parameter.
|
|||||||
|
|
||||||
## List of Error Codes
|
## List of Error Codes
|
||||||
|
|
||||||
| Code | Description | Parameters |
|
| Code | Description | Parameters |
|
||||||
| ---- | -------------------------------------- | ---------- |
|
| ---- | ----------------------------------------------------------------------- | ----------------- |
|
||||||
| 10 | Unable to parse browser request length | |
|
| 10 | Unable to parse browser request length | error |
|
||||||
| 11 | Unable to parse browser request | |
|
| 11 | Unable to parse browser request | error |
|
||||||
| 12 | Invalid request action | action |
|
| 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
|
## Settings
|
||||||
|
|
||||||
@@ -106,7 +111,7 @@ is alive, determine the version at startup, and provide per-store defaults.
|
|||||||
"data": {
|
"data": {
|
||||||
"defaultStore": {
|
"defaultStore": {
|
||||||
"path": "/path/to/default/store",
|
"path": "/path/to/default/store",
|
||||||
"defaultSettings": "<raw contents of $defaultPath/.browserpass.json>",
|
"settings": "<raw contents of $defaultPath/.browserpass.json>",
|
||||||
},
|
},
|
||||||
“storeSettings”: {
|
“storeSettings”: {
|
||||||
“storeName”: "<raw contents of storePath/.browserpass.json>"
|
“storeName”: "<raw contents of storePath/.browserpass.json>"
|
||||||
|
@@ -10,14 +10,29 @@ type Code int
|
|||||||
// Error codes that are sent to the browser extension and used as exit codes in the app.
|
// 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.
|
// DO NOT MODIFY THE VALUES, always append new error codes to the bottom.
|
||||||
const (
|
const (
|
||||||
// CodeParseRequestLength error parsing request length
|
// CodeParseRequestLength error parsing a request length
|
||||||
CodeParseRequestLength Code = 10
|
CodeParseRequestLength Code = 10
|
||||||
|
|
||||||
// CodeParseRequest error parsing request
|
// CodeParseRequest error parsing a request
|
||||||
CodeParseRequest Code = 11
|
CodeParseRequest Code = 11
|
||||||
|
|
||||||
// CodeInvalidRequestAction error parsing request action
|
// CodeInvalidRequestAction error parsing a request action
|
||||||
CodeInvalidRequestAction = 12
|
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
|
// ExitWithCode exit with error code
|
||||||
|
30
request/common.go
Normal file
30
request/common.go
Normal 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
126
request/configure.go
Normal 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
|
||||||
|
}
|
@@ -12,8 +12,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type request struct {
|
type request struct {
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
Settings interface{} `json:"settings"`
|
Settings struct {
|
||||||
|
GpgPath string `json:"gpgPath"`
|
||||||
|
Stores map[string]struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
} `json:"settings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process handles browser request
|
// Process handles browser request
|
||||||
@@ -23,14 +29,18 @@ func Process() {
|
|||||||
|
|
||||||
switch request.Action {
|
switch request.Action {
|
||||||
case "configure":
|
case "configure":
|
||||||
break
|
configure(request)
|
||||||
case "list":
|
case "list":
|
||||||
break
|
break
|
||||||
case "fetch":
|
case "fetch":
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
log.Errorf("Received a browser request with an unknown action: %+v", request)
|
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)
|
errors.ExitWithCode(errors.CodeInvalidRequestAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +54,11 @@ func parseRequestLength() uint32 {
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
log.Error("Unable to parse the length of the browser request: ", err)
|
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)
|
errors.ExitWithCode(errors.CodeParseRequestLength)
|
||||||
}
|
}
|
||||||
return length
|
return length
|
||||||
@@ -56,7 +70,11 @@ func parseRequest(messageLength uint32) request {
|
|||||||
reader := &io.LimitedReader{R: os.Stdin, N: int64(messageLength)}
|
reader := &io.LimitedReader{R: os.Stdin, N: int64(messageLength)}
|
||||||
if err := json.NewDecoder(reader).Decode(&parsed); err != nil {
|
if err := json.NewDecoder(reader).Decode(&parsed); err != nil {
|
||||||
log.Error("Unable to parse the browser request: ", err)
|
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)
|
errors.ExitWithCode(errors.CodeParseRequest)
|
||||||
}
|
}
|
||||||
return parsed
|
return parsed
|
@@ -24,6 +24,31 @@ type errorResponse struct {
|
|||||||
Params interface{} `json:"params"`
|
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
|
// 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) {
|
func SendError(errorCode errors.Code, errorMsg string, extraParams *map[string]string) {
|
||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
|
Reference in New Issue
Block a user