github.com/Night-mk/quorum@v21.1.0+incompatible/rpc/security.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/ethereum/go-ethereum/log"
    11  	"github.com/golang/protobuf/ptypes"
    12  	"github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto"
    13  )
    14  
    15  type securityContextKey string
    16  type securityContext context.Context
    17  
    18  const (
    19  	HttpAuthorizationHeader = "Authorization"
    20  	// this key is exported for WS transport
    21  	CtxCredentialsProvider = securityContextKey("CREDENTIALS_PROVIDER") // key to save reference to rpc.HttpCredentialsProviderFunc
    22  	// keys used to save values in request context
    23  	ctxAuthenticationError   = securityContextKey("AUTHENTICATION_ERROR")   // key to save error during authentication before processing the request body
    24  	CtxPreauthenticatedToken = securityContextKey("PREAUTHENTICATED_TOKEN") // key to save the preauthenticated token once authenticated
    25  )
    26  
    27  type securityContextConfigurer interface {
    28  	Configure(secCtx securityContext)
    29  }
    30  
    31  type securityContextResolver interface {
    32  	Resolve() securityContext
    33  }
    34  
    35  type securityError struct{ message string }
    36  
    37  // Provider function to return token being injected in Authorization http request header
    38  type HttpCredentialsProviderFunc func(ctx context.Context) (string, error)
    39  
    40  func (e *securityError) ErrorCode() int { return -32001 }
    41  
    42  func (e *securityError) Error() string { return e.message }
    43  
    44  func extractToken(req *http.Request) (string, bool) {
    45  	token := req.Header.Get(HttpAuthorizationHeader)
    46  	return token, token != ""
    47  }
    48  
    49  func verifyExpiration(token *proto.PreAuthenticatedAuthenticationToken) error {
    50  	if token == nil {
    51  		return nil
    52  	}
    53  	expiredAt, err := ptypes.Timestamp(token.ExpiredAt)
    54  	if err != nil {
    55  		return fmt.Errorf("invalid timestamp in token: %s", err)
    56  	}
    57  	if time.Now().Before(expiredAt) {
    58  		return nil
    59  	}
    60  	return &securityError{"token expired"}
    61  }
    62  
    63  func verifyAccess(service, method string, authorities []*proto.GrantedAuthority) error {
    64  	for _, authority := range authorities {
    65  		if authority.Service == "*" && authority.Method == "*" {
    66  			return nil
    67  		}
    68  		if authority.Service == "*" && authority.Method == method {
    69  			return nil
    70  		}
    71  		if authority.Service == service && authority.Method == "*" {
    72  			return nil
    73  		}
    74  		if authority.Service == service && authority.Method == method {
    75  			return nil
    76  		}
    77  	}
    78  	return &securityError{fmt.Sprintf("%s%s%s - access denied", service, serviceMethodSeparator, method)}
    79  }
    80  
    81  // verify if a call is authorized using information available in the security context
    82  // it also checks for token expiration. That means if this is called multiple times (batch processing),
    83  // token expiration is checked multiple times.
    84  func secureCall(resolver securityContextResolver, msg *jsonrpcMessage) error {
    85  	secCtx := resolver.Resolve()
    86  	if secCtx == nil {
    87  		return nil
    88  	}
    89  	if err, hasError := secCtx.Value(ctxAuthenticationError).(error); hasError {
    90  		return err
    91  	}
    92  	if authToken, isPreauthenticated := secCtx.Value(CtxPreauthenticatedToken).(*proto.PreAuthenticatedAuthenticationToken); isPreauthenticated {
    93  		if err := verifyExpiration(authToken); err != nil {
    94  			return err
    95  		}
    96  		elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2)
    97  		if len(elem) != 2 {
    98  			log.Warn("unsupported method when performing authorization check", "method", msg.Method)
    99  		} else if err := verifyAccess(elem[0], elem[1], authToken.Authorities); err != nil {
   100  			return err
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  // construct JSON RPC error message which has the ID of the request
   107  func securityErrorMessage(forMsg *jsonrpcMessage, err error) *jsonrpcMessage {
   108  	msg := &jsonrpcMessage{Version: vsn, ID: forMsg.ID, Error: &jsonError{
   109  		Code:    defaultErrorCode,
   110  		Message: err.Error(),
   111  	}}
   112  	ec, ok := err.(Error)
   113  	if ok {
   114  		msg.Error.Code = ec.ErrorCode()
   115  	}
   116  	return msg
   117  }