Skip to content

Commit

Permalink
[FAB-11200] Create an errors package
Browse files Browse the repository at this point in the history
Error related code moved to its own separate package, now it
can be referenced from different packages.

Change-Id: I2e7901a4cb20608066fbe2986799d5d987700fbe
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim authored and mastersingh24 committed Jul 29, 2018
1 parent 49d3936 commit 1eb786b
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 243 deletions.
11 changes: 6 additions & 5 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
cflocalsigner "github.com/cloudflare/cfssl/signer/local"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/attr"
"github.com/hyperledger/fabric-ca/lib/caerrors"
"github.com/hyperledger/fabric-ca/lib/common"
"github.com/hyperledger/fabric-ca/lib/dbutil"
"github.com/hyperledger/fabric-ca/lib/ldap"
Expand Down Expand Up @@ -165,7 +166,7 @@ func (ca *CA) init(renew bool) (err error) {
if err != nil {
log.Error("Error occurred initializing database: ", err)
// Return if a server configuration error encountered (e.g. Invalid max enrollment for a bootstrap user)
if isFatalError(err) {
if caerrors.IsFatalError(err) {
return err
}
}
Expand Down Expand Up @@ -250,7 +251,7 @@ func (ca *CA) initKeyMaterial(renew bool) error {
}
return nil
}
log.Warning(newServerError(ErrCACertFileNotFound, "The specified CA certificate file %s does not exist", certFile))
log.Warning(caerrors.NewServerError(caerrors.ErrCACertFileNotFound, "The specified CA certificate file %s does not exist", certFile))
}

// Get the CA cert
Expand Down Expand Up @@ -641,7 +642,7 @@ func (ca *CA) initDB() error {
if err != nil {
log.Error(err)
dbError = true
if isFatalError(err) {
if caerrors.IsFatalError(err) {
return err
}
}
Expand Down Expand Up @@ -826,7 +827,7 @@ func (ca *CA) addIdentity(id *CAConfigIdentity, errIfFound bool) error {

id.MaxEnrollments, err = getMaxEnrollments(id.MaxEnrollments, ca.Config.Registry.MaxEnrollments)
if err != nil {
return newFatalError(ErrConfig, "Configuration Error: %s", err)
return caerrors.NewFatalError(caerrors.ErrConfig, "Configuration Error: %s", err)
}

attrs, err := attr.ConvertAttrs(id.Attrs)
Expand Down Expand Up @@ -1237,7 +1238,7 @@ func (ca *CA) checkDBLevels() error {
affVer := getIntLevel(levels, "affiliation")
certVer := getIntLevel(levels, "certificate")
if (idVer > sl.Identity) || (affVer > sl.Affiliation) || (certVer > sl.Certificate) {
return newFatalError(ErrDBLevel, "The version of the database is newer than the server version. Upgrade your server.")
return caerrors.NewFatalError(caerrors.ErrDBLevel, "The version of the database is newer than the server version. Upgrade your server.")
}
return nil
}
Expand Down
98 changes: 66 additions & 32 deletions lib/servererror.go → lib/caerrors/servererror.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package lib
package caerrors

import (
"encoding/json"
Expand Down Expand Up @@ -165,10 +165,10 @@ const (
ErrAuthorizationFailure = 71
)

// Construct a new HTTP error.
func createHTTPErr(scode, code int, format string, args ...interface{}) *httpErr {
// CreateHTTPErr constructs a new HTTP error.
func CreateHTTPErr(scode, code int, format string, args ...interface{}) *HTTPErr {
msg := fmt.Sprintf(format, args...)
return &httpErr{
return &HTTPErr{
scode: scode,
lcode: code,
lmsg: msg,
Expand All @@ -177,36 +177,36 @@ func createHTTPErr(scode, code int, format string, args ...interface{}) *httpErr
}
}

// Construct a new HTTP error wrappered with pkg/errors error.
func newHTTPErr(scode, code int, format string, args ...interface{}) error {
return errors.Wrap(createHTTPErr(scode, code, format, args...), "")
// NewHTTPErr constructs a new HTTP error wrappered with pkg/errors error.
func NewHTTPErr(scode, code int, format string, args ...interface{}) error {
return errors.Wrap(CreateHTTPErr(scode, code, format, args...), "")
}

// Construct an HTTP error specifically indicating an authentication failure.
// NewAuthenticationErr constructs an HTTP error specifically indicating an authentication failure.
// The local code and message is specific, but the remote code and message is generic
// for security reasons.
func newAuthenticationErr(code int, format string, args ...interface{}) error {
he := createHTTPErr(401, code, format, args...)
func NewAuthenticationErr(code int, format string, args ...interface{}) error {
he := CreateHTTPErr(401, code, format, args...)
he.Remote(ErrAuthenticationFailure, "Authentication failure")
return errors.Wrap(he, "")
}

// Construct an HTTP error specifically indicating an authorization failure.
// NewAuthorizationErr constructs an HTTP error specifically indicating an authorization failure.
// The local code and message is specific, but the remote code and message is generic
// for security reasons.
func newAuthorizationErr(code int, format string, args ...interface{}) error {
he := createHTTPErr(403, code, format, args...)
func NewAuthorizationErr(code int, format string, args ...interface{}) error {
he := CreateHTTPErr(403, code, format, args...)
he.Remote(ErrAuthorizationFailure, "Authorization failure")
return errors.Wrap(he, "")
}

// httpErr is an HTTP error.
// HTTPErr is an HTTP error.
// "local" refers to errors as logged in the server (local to the server).
// "remote" refers to errors as returned to the client (remote to the server).
// This allows us to log a more specific error in the server logs while
// returning a more generic error to the client, as is done for authorization
// failures.
type httpErr struct {
type HTTPErr struct {
scode int // HTTP status code
lcode int // local error code
lmsg string // local error message
Expand All @@ -215,28 +215,32 @@ type httpErr struct {
}

// Error returns the string representation
func (he *httpErr) Error() string {
func (he *HTTPErr) Error() string {
return he.String()
}

// String returns a string representation of this augmented error
func (he *httpErr) String() string {
func (he *HTTPErr) String() string {
if he.lcode == he.rcode && he.lmsg == he.rmsg {
return fmt.Sprintf("scode: %d, code: %d, msg: %s", he.scode, he.lcode, he.lmsg)
}
return fmt.Sprintf("scode: %d, local code: %d, local msg: %s, remote code: %d, remote msg: %s",
he.scode, he.lcode, he.lmsg, he.rcode, he.rmsg)
}

// Set the remote code and message to something different from that of the local code and message
func (he *httpErr) Remote(code int, format string, args ...interface{}) *httpErr {
// Remote sets the remote code and message to something different from that of the local code and message
func (he *HTTPErr) Remote(code int, format string, args ...interface{}) *HTTPErr {
he.rcode = code
he.rmsg = fmt.Sprintf(format, args...)
return he
}

type errorWriter interface {
http.ResponseWriter
}

// Write the server's HTTP error response
func (he *httpErr) writeResponse(w http.ResponseWriter) error {
func (he *HTTPErr) writeResponse(w errorWriter) error {
response := cfsslapi.NewErrorResponse(he.rmsg, he.rcode)
jsonMessage, err := json.Marshal(response)
if err != nil {
Expand All @@ -248,50 +252,80 @@ func (he *httpErr) writeResponse(w http.ResponseWriter) error {
return nil
}

type serverErr struct {
// GetRemoteCode returns the remote error code
func (he *HTTPErr) GetRemoteCode() int {
return he.rcode
}

// GetLocalCode returns the local error code
func (he *HTTPErr) GetLocalCode() int {
return he.lcode
}

// GetStatusCode returns the HTTP status code
func (he *HTTPErr) GetStatusCode() int {
return he.scode
}

// GetRemoteMsg returns the remote error message
func (he *HTTPErr) GetRemoteMsg() string {
return he.rmsg
}

// GetLocalMsg returns the remote error message
func (he *HTTPErr) GetLocalMsg() string {
return he.lmsg
}

// ServerErr contains error message with corresponding CA error code
type ServerErr struct {
code int
msg string
}

type fatalErr struct {
serverErr
// FatalErr is a server error that is will prevent the server/CA from continuing to operate
type FatalErr struct {
ServerErr
}

func newServerError(code int, format string, args ...interface{}) *serverErr {
// NewServerError constructs a server error
func NewServerError(code int, format string, args ...interface{}) *ServerErr {
msg := fmt.Sprintf(format, args...)
return &serverErr{
return &ServerErr{
code: code,
msg: msg,
}
}

func newFatalError(code int, format string, args ...interface{}) *fatalErr {
// NewFatalError constructs a fatal error
func NewFatalError(code int, format string, args ...interface{}) *FatalErr {
msg := fmt.Sprintf(format, args...)
return &fatalErr{
serverErr{
return &FatalErr{
ServerErr{
code: code,
msg: msg,
},
}
}

func (fe *fatalErr) Error() string {
func (fe *FatalErr) Error() string {
return fe.String()
}

func (fe *fatalErr) String() string {
func (fe *FatalErr) String() string {
return fmt.Sprintf("Code: %d - %s", fe.code, fe.msg)
}

func isFatalError(err error) bool {
// IsFatalError return true if the error is of type 'FatalErr'
func IsFatalError(err error) bool {
causeErr := errors.Cause(err)
typ := reflect.TypeOf(causeErr)
// If a pointer to a struct is passe, get the type of the dereferenced object
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}

if typ == reflect.TypeOf(fatalErr{}) {
if typ == reflect.TypeOf(FatalErr{}) {
return true
}
return false
Expand Down
96 changes: 96 additions & 0 deletions lib/caerrors/servererror_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package caerrors

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestErrorString(t *testing.T) {
msg := "message"
err := NewHTTPErr(400, ErrMethodNotAllowed, "%s", msg)
errMsg := err.Error()
assert.Contains(t, errMsg, msg)
}

func TestHTTPErr(t *testing.T) {
msg := "message"
err := CreateHTTPErr(400, ErrMethodNotAllowed, "%s", msg)
errMsg := err.Error()
assert.Contains(t, errMsg, msg)
assert.Equal(t, err.GetRemoteCode(), ErrMethodNotAllowed)
assert.Equal(t, err.GetLocalCode(), ErrMethodNotAllowed)
assert.Equal(t, err.GetRemoteMsg(), "message")
assert.Equal(t, err.GetStatusCode(), 400)
assert.Equal(t, err.GetLocalMsg(), "message")

w := &mockHTTPWriter{httptest.NewRecorder()}
assert.NoError(t, err.writeResponse(w))
}

func TestRemoteErrorString(t *testing.T) {
lmsg := "local message"
rmsg := "remote message"
err := CreateHTTPErr(401, ErrMethodNotAllowed, "%s", lmsg).Remote(ErrUnknown, rmsg)
errMsg := err.Error()
assert.Contains(t, errMsg, rmsg)
}

func TestNewAuthenticationError(t *testing.T) {
lmsg := "local message"
err := NewAuthenticationErr(ErrAuthenticationFailure, "%s", lmsg)
errMsg := err.Error()
assert.Contains(t, errMsg, "Authentication failure")
}

func TestNewAuthorizationError(t *testing.T) {
lmsg := "local message"
err := NewAuthorizationErr(ErrAuthorizationFailure, "%s", lmsg)
errMsg := err.Error()
assert.Contains(t, errMsg, "Authorization failure")
}

func TestServerError(t *testing.T) {
err := NewServerError(24, "error: %s", "server")
assert.Equal(t, err.code, 24)
assert.Equal(t, err.msg, "error: server")
}

func TestFatalError(t *testing.T) {
err := NewFatalError(25, "fatal error: %s", "server")
assert.Equal(t, err.code, 25)
assert.Equal(t, err.msg, "fatal error: server")

assert.Equal(t, err.Error(), "Code: 25 - fatal error: server")
}

func TestIsFatalError(t *testing.T) {
ferr := NewFatalError(25, "fatal error: %s", "server")
assert.Equal(t, IsFatalError(ferr), true)

err := NewAuthorizationErr(25, "%s", "auth error")
assert.Equal(t, IsFatalError(err), false)
}

type mockHTTPWriter struct {
http.ResponseWriter
}

// Header returns the header map that will be sent by WriteHeader.
func (m *mockHTTPWriter) Header() http.Header {
return m.ResponseWriter.Header()
}

// Write writes the data to the connection as part of an HTTP reply.
func (m *mockHTTPWriter) Write(buf []byte) (int, error) {
w := m.ResponseWriter
return w.Write(buf)
}
Loading

0 comments on commit 1eb786b

Please sign in to comment.