Implement parsing browser requests and sending errors (#9)
This commit is contained in:
1
Makefile
1
Makefile
@@ -1,3 +1,4 @@
|
|||||||
|
.PHONY: all
|
||||||
all: deps browserpass test
|
all: deps browserpass test
|
||||||
|
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
|
@@ -41,9 +41,11 @@ 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 | |
|
||||||
|
| 11 | Unable to parse browser request | |
|
||||||
|
| 12 | Invalid request action | action |
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
|
26
errors/errors.go
Normal file
26
errors/errors.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Code exit code
|
||||||
|
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 Code = 10
|
||||||
|
|
||||||
|
// CodeParseRequest error parsing request
|
||||||
|
CodeParseRequest Code = 11
|
||||||
|
|
||||||
|
// CodeInvalidRequestAction error parsing request action
|
||||||
|
CodeInvalidRequestAction = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExitWithCode exit with error code
|
||||||
|
func ExitWithCode(code Code) {
|
||||||
|
os.Exit(int(code))
|
||||||
|
}
|
27
main.go
27
main.go
@@ -5,35 +5,34 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/maximbaz/browserpass-native/openbsd"
|
"github.com/browserpass/browserpass-native/openbsd"
|
||||||
"github.com/maximbaz/browserpass-native/persistentlog"
|
"github.com/browserpass/browserpass-native/persistentlog"
|
||||||
|
"github.com/browserpass/browserpass-native/request"
|
||||||
|
"github.com/browserpass/browserpass-native/version"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VERSION host app version
|
|
||||||
const VERSION = "3.0.0"
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var verbose bool
|
var isVerbose bool
|
||||||
var version bool
|
var isVersion bool
|
||||||
flag.BoolVar(&verbose, "v", false, "print verbose output")
|
flag.BoolVar(&isVerbose, "v", false, "print verbose output")
|
||||||
flag.BoolVar(&version, "version", false, "print version and exit")
|
flag.BoolVar(&isVersion, "version", false, "print version and exit")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if version {
|
if isVersion {
|
||||||
fmt.Println("Browserpass host app version:", VERSION)
|
fmt.Println("Browserpass host app version:", version.String())
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
openbsd.Pledge("stdio rpath proc exec")
|
openbsd.Pledge("stdio rpath proc exec")
|
||||||
|
|
||||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
|
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
|
||||||
if verbose {
|
if isVerbose {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
persistentlog.AddPersistentLogHook()
|
persistentlog.AddPersistentLogHook()
|
||||||
|
|
||||||
log.Debugf("Starting browserpass host app v%v", VERSION)
|
log.Debugf("Starting browserpass host app v%v", version.String())
|
||||||
process()
|
request.Process()
|
||||||
}
|
}
|
||||||
|
@@ -11,11 +11,9 @@ import (
|
|||||||
|
|
||||||
// AddPersistentLogHook configures persisting logs in syslog
|
// AddPersistentLogHook configures persisting logs in syslog
|
||||||
func AddPersistentLogHook() {
|
func AddPersistentLogHook() {
|
||||||
hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass")
|
if hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass"); err != nil {
|
||||||
|
log.Warn("Unable to connect to syslog, logs will NOT be persisted: ", err)
|
||||||
if err == nil {
|
|
||||||
log.AddHook(hook)
|
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Unable to connect to syslog, logs will NOT be persisted")
|
log.AddHook(hook)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ func AddPersistentLogHook() {
|
|||||||
}
|
}
|
||||||
logFolderPath := filepath.Join(appDataPath, "browserpass")
|
logFolderPath := filepath.Join(appDataPath, "browserpass")
|
||||||
if err := os.MkdirAll(logFolderPath, os.ModePerm); err != nil {
|
if err := os.MkdirAll(logFolderPath, os.ModePerm); err != nil {
|
||||||
log.Warn("Unable to create browserpass folder in %%APPDATA%%, logs will NOT be persisted")
|
log.Warn("Unable to create browserpass folder in %%APPDATA%%, logs will NOT be persisted: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logFilePath := filepath.Join(logFolderPath, "browserpass.log")
|
logFilePath := filepath.Join(logFolderPath, "browserpass.log")
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
func process() {
|
|
||||||
// read stdin, process request, print to stdout...
|
|
||||||
}
|
|
63
request/request.go
Normal file
63
request/request.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/browserpass/browserpass-native/errors"
|
||||||
|
"github.com/browserpass/browserpass-native/response"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Settings interface{} `json:"settings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process handles browser request
|
||||||
|
func Process() {
|
||||||
|
requestLength := parseRequestLength()
|
||||||
|
request := parseRequest(requestLength)
|
||||||
|
|
||||||
|
switch request.Action {
|
||||||
|
case "configure":
|
||||||
|
break
|
||||||
|
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})
|
||||||
|
errors.ExitWithCode(errors.CodeInvalidRequestAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request length is the first 4 bytes in LittleEndian encoding on stdin
|
||||||
|
func parseRequestLength() uint32 {
|
||||||
|
var length uint32
|
||||||
|
if err := binary.Read(os.Stdin, binary.LittleEndian, &length); err != nil {
|
||||||
|
// TODO: Original browserpass ignores EOF as if it is expected, is it true?
|
||||||
|
// if err == io.EOF {
|
||||||
|
// 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)
|
||||||
|
errors.ExitWithCode(errors.CodeParseRequestLength)
|
||||||
|
}
|
||||||
|
return length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request is a json with a predefined structure
|
||||||
|
func parseRequest(messageLength uint32) request {
|
||||||
|
var parsed 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)
|
||||||
|
errors.ExitWithCode(errors.CodeParseRequest)
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
65
response/response.go
Normal file
65
response/response.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/browserpass/browserpass-native/errors"
|
||||||
|
"github.com/browserpass/browserpass-native/version"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type okResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Code errors.Code `json:"code"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
Params interface{} `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
"message": errorMsg,
|
||||||
|
}
|
||||||
|
if extraParams != nil {
|
||||||
|
for key, value := range *extraParams {
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send(&errorResponse{
|
||||||
|
Status: "error",
|
||||||
|
Code: errorCode,
|
||||||
|
Version: version.Code,
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(data interface{}) {
|
||||||
|
switch data.(type) {
|
||||||
|
case *okResponse:
|
||||||
|
case *errorResponse:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
log.Fatalf("Only data of type OkResponse and ErrorResponse is allowed to be sent to the browser extension, attempted to send: %+v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesBuffer bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&bytesBuffer).Encode(data); err != nil {
|
||||||
|
log.Fatal("Unable to encode data for sending: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Write(os.Stdout, binary.LittleEndian, uint32(bytesBuffer.Len())); err != nil {
|
||||||
|
log.Fatal("Unable to send the length of the response data: ", err)
|
||||||
|
}
|
||||||
|
if _, err := bytesBuffer.WriteTo(os.Stdout); err != nil {
|
||||||
|
log.Fatal("Unable to send the response data: ", err)
|
||||||
|
}
|
||||||
|
}
|
15
version/version.go
Normal file
15
version/version.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const major = 3
|
||||||
|
const minor = 0
|
||||||
|
const patch = 0
|
||||||
|
|
||||||
|
// Code version as integer
|
||||||
|
const Code = major*1000000 + minor*1000 + patch
|
||||||
|
|
||||||
|
// String version as string
|
||||||
|
func String() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", major, minor, patch)
|
||||||
|
}
|
Reference in New Issue
Block a user