Implement parsing browser requests and sending errors (#9)

This commit is contained in:
Maxim Baz 2018-04-14 12:49:03 +02:00 committed by GitHub
parent 60a4a1ddaa
commit 42e6157238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 28 deletions

View File

@ -1,3 +1,4 @@
.PHONY: all
all: deps browserpass test
.PHONY: deps

View File

@ -41,9 +41,11 @@ should be supplied as a `message` parameter.
## 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

26
errors/errors.go Normal file
View 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
View File

@ -5,35 +5,34 @@ import (
"fmt"
"os"
"github.com/maximbaz/browserpass-native/openbsd"
"github.com/maximbaz/browserpass-native/persistentlog"
"github.com/browserpass/browserpass-native/openbsd"
"github.com/browserpass/browserpass-native/persistentlog"
"github.com/browserpass/browserpass-native/request"
"github.com/browserpass/browserpass-native/version"
log "github.com/sirupsen/logrus"
)
// VERSION host app version
const VERSION = "3.0.0"
func main() {
var verbose bool
var version bool
flag.BoolVar(&verbose, "v", false, "print verbose output")
flag.BoolVar(&version, "version", false, "print version and exit")
var isVerbose bool
var isVersion bool
flag.BoolVar(&isVerbose, "v", false, "print verbose output")
flag.BoolVar(&isVersion, "version", false, "print version and exit")
flag.Parse()
if version {
fmt.Println("Browserpass host app version:", VERSION)
if isVersion {
fmt.Println("Browserpass host app version:", version.String())
os.Exit(0)
}
openbsd.Pledge("stdio rpath proc exec")
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
if verbose {
if isVerbose {
log.SetLevel(log.DebugLevel)
}
persistentlog.AddPersistentLogHook()
log.Debugf("Starting browserpass host app v%v", VERSION)
process()
log.Debugf("Starting browserpass host app v%v", version.String())
request.Process()
}

View File

@ -11,11 +11,9 @@ import (
// AddPersistentLogHook configures persisting logs in syslog
func AddPersistentLogHook() {
hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass")
if err == nil {
log.AddHook(hook)
if hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass"); err != nil {
log.Warn("Unable to connect to syslog, logs will NOT be persisted: ", err)
} else {
log.Warn("Unable to connect to syslog, logs will NOT be persisted")
log.AddHook(hook)
}
}

View File

@ -19,7 +19,7 @@ func AddPersistentLogHook() {
}
logFolderPath := filepath.Join(appDataPath, "browserpass")
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
}
logFilePath := filepath.Join(logFolderPath, "browserpass.log")

View File

@ -1,5 +0,0 @@
package main
func process() {
// read stdin, process request, print to stdout...
}

63
request/request.go Normal file
View 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
View 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
View 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)
}