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 }