github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/orderer/common/channelparticipation/restapi.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package channelparticipation
     8  
     9  import (
    10  	"encoding/json"
    11  	"io/ioutil"
    12  	"mime"
    13  	"mime/multipart"
    14  	"net/http"
    15  	"path"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/golang/protobuf/proto"
    20  	"github.com/gorilla/mux"
    21  	cb "github.com/hyperledger/fabric-protos-go/common"
    22  	"github.com/osdi23p228/fabric/common/configtx"
    23  	"github.com/osdi23p228/fabric/common/flogging"
    24  	"github.com/osdi23p228/fabric/orderer/common/localconfig"
    25  	"github.com/osdi23p228/fabric/orderer/common/types"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  const (
    30  	URLBaseV1              = "/participation/v1/"
    31  	URLBaseV1Channels      = URLBaseV1 + "channels"
    32  	FormDataConfigBlockKey = "config-block"
    33  	RemoveStorageQueryKey  = "removeStorage"
    34  
    35  	channelIDKey        = "channelID"
    36  	urlWithChannelIDKey = URLBaseV1Channels + "/{" + channelIDKey + "}"
    37  )
    38  
    39  //go:generate counterfeiter -o mocks/channel_management.go -fake-name ChannelManagement . ChannelManagement
    40  
    41  type ChannelManagement interface {
    42  	// ChannelList returns a slice of ChannelInfoShort containing all application channels (excluding the system
    43  	// channel), and ChannelInfoShort of the system channel (nil if does not exist).
    44  	// The URL fields are empty, and are to be completed by the caller.
    45  	ChannelList() types.ChannelList
    46  
    47  	// ChannelInfo provides extended status information about a channel.
    48  	// The URL field is empty, and is to be completed by the caller.
    49  	ChannelInfo(channelID string) (types.ChannelInfo, error)
    50  
    51  	// JoinChannel instructs the orderer to create a channel and join it with the provided config block.
    52  	JoinChannel(channelID string, configBlock *cb.Block, isAppChannel bool) (types.ChannelInfo, error)
    53  
    54  	// RemoveChannel instructs the orderer to remove a channel.
    55  	// Depending on the removeStorage parameter, the storage resources are either removed or archived.
    56  	RemoveChannel(channelID string, removeStorage bool) error
    57  }
    58  
    59  // HTTPHandler handles all the HTTP requests to the channel participation API.
    60  type HTTPHandler struct {
    61  	logger    *flogging.FabricLogger
    62  	config    localconfig.ChannelParticipation
    63  	registrar ChannelManagement
    64  	router    *mux.Router
    65  	// TODO skeleton
    66  }
    67  
    68  func NewHTTPHandler(config localconfig.ChannelParticipation, registrar ChannelManagement) *HTTPHandler {
    69  	handler := &HTTPHandler{
    70  		logger:    flogging.MustGetLogger("orderer.commmon.channelparticipation"),
    71  		config:    config,
    72  		registrar: registrar,
    73  		router:    mux.NewRouter(),
    74  	}
    75  
    76  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveListOne).Methods(http.MethodGet)
    77  
    78  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveJoin).Methods(http.MethodPost).HeadersRegexp(
    79  		"Content-Type", "multipart/form-data*")
    80  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveBadContentType).Methods(http.MethodPost)
    81  
    82  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveRemove).Methods(http.MethodDelete)
    83  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveNotAllowed)
    84  
    85  	handler.router.HandleFunc(URLBaseV1Channels, handler.serveListAll).Methods(http.MethodGet)
    86  	handler.router.HandleFunc(URLBaseV1Channels, handler.serveNotAllowed)
    87  
    88  	handler.router.HandleFunc(URLBaseV1, handler.redirectBaseV1).Methods(http.MethodGet)
    89  
    90  	return handler
    91  }
    92  
    93  func (h *HTTPHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
    94  	if !h.config.Enabled {
    95  		err := errors.New("channel participation API is disabled")
    96  		h.sendResponseJsonError(resp, http.StatusServiceUnavailable, err)
    97  		return
    98  	}
    99  
   100  	h.router.ServeHTTP(resp, req)
   101  }
   102  
   103  // List all channels
   104  func (h *HTTPHandler) serveListAll(resp http.ResponseWriter, req *http.Request) {
   105  	_, err := negotiateContentType(req) // Only application/json responses for now
   106  	if err != nil {
   107  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   108  		return
   109  	}
   110  	channelList := h.registrar.ChannelList()
   111  	if channelList.SystemChannel != nil && channelList.SystemChannel.Name != "" {
   112  		channelList.SystemChannel.URL = path.Join(URLBaseV1Channels, channelList.SystemChannel.Name)
   113  	}
   114  	for i, info := range channelList.Channels {
   115  		channelList.Channels[i].URL = path.Join(URLBaseV1Channels, info.Name)
   116  	}
   117  	resp.Header().Set("Cache-Control", "no-store")
   118  	h.sendResponseOK(resp, channelList)
   119  }
   120  
   121  // List a single channel
   122  func (h *HTTPHandler) serveListOne(resp http.ResponseWriter, req *http.Request) {
   123  	_, err := negotiateContentType(req) // Only application/json responses for now
   124  	if err != nil {
   125  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   126  		return
   127  	}
   128  
   129  	channelID, err := h.extractChannelID(req, resp)
   130  	if err != nil {
   131  		return
   132  	}
   133  
   134  	infoFull, err := h.registrar.ChannelInfo(channelID)
   135  	if err != nil {
   136  		h.sendResponseJsonError(resp, http.StatusNotFound, err)
   137  		return
   138  	}
   139  	resp.Header().Set("Cache-Control", "no-store")
   140  	h.sendResponseOK(resp, infoFull)
   141  }
   142  
   143  func (h *HTTPHandler) redirectBaseV1(resp http.ResponseWriter, req *http.Request) {
   144  	http.Redirect(resp, req, URLBaseV1Channels, http.StatusFound)
   145  }
   146  
   147  // Join a channel.
   148  // Expect multipart/form-data.
   149  func (h *HTTPHandler) serveJoin(resp http.ResponseWriter, req *http.Request) {
   150  	_, err := negotiateContentType(req) // Only application/json responses for now
   151  	if err != nil {
   152  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   153  		return
   154  	}
   155  
   156  	channelID, err := h.extractChannelID(req, resp)
   157  	if err != nil {
   158  		return
   159  	}
   160  
   161  	_, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
   162  	if err != nil {
   163  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot parse Mime media type"))
   164  		return
   165  	}
   166  
   167  	block := h.multipartFormDataBodyToBlock(params, req, resp)
   168  	if block == nil {
   169  		return
   170  	}
   171  
   172  	isAppChannel, err := ValidateJoinBlock(channelID, block)
   173  	if err != nil {
   174  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "invalid join block"))
   175  		return
   176  	}
   177  
   178  	info, err := h.registrar.JoinChannel(channelID, block, isAppChannel)
   179  	if err == nil {
   180  		info.URL = path.Join(URLBaseV1Channels, info.Name)
   181  		h.logger.Debugf("Successfully joined channel: %s", info)
   182  		h.sendResponseCreated(resp, info.URL, info)
   183  		return
   184  	}
   185  
   186  	h.sendJoinError(err, resp)
   187  }
   188  
   189  // Expect a multipart/form-data with a single part, of type file, with key FormDataConfigBlockKey.
   190  func (h *HTTPHandler) multipartFormDataBodyToBlock(params map[string]string, req *http.Request, resp http.ResponseWriter) *cb.Block {
   191  	boundary := params["boundary"]
   192  	reader := multipart.NewReader(req.Body, boundary)
   193  	form, err := reader.ReadForm(100 * 1024 * 1024)
   194  	if err != nil {
   195  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot read form from request body"))
   196  		return nil
   197  	}
   198  
   199  	if _, exist := form.File[FormDataConfigBlockKey]; !exist {
   200  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Errorf("form does not contains part key: %s", FormDataConfigBlockKey))
   201  		return nil
   202  	}
   203  
   204  	if len(form.File) != 1 || len(form.Value) != 0 {
   205  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.New("form contains too many parts"))
   206  		return nil
   207  	}
   208  
   209  	fileHeader := form.File[FormDataConfigBlockKey][0]
   210  	file, err := fileHeader.Open()
   211  	if err != nil {
   212  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot open file part %s from request body", FormDataConfigBlockKey))
   213  		return nil
   214  	}
   215  
   216  	blockBytes, err := ioutil.ReadAll(file)
   217  	if err != nil {
   218  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot read file part %s from request body", FormDataConfigBlockKey))
   219  		return nil
   220  	}
   221  
   222  	block := &cb.Block{}
   223  	err = proto.Unmarshal(blockBytes, block)
   224  	if err != nil {
   225  		h.logger.Debugf("Failed to unmarshal blockBytes: %s", err)
   226  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot unmarshal file part %s into a block", FormDataConfigBlockKey))
   227  		return nil
   228  	}
   229  
   230  	return block
   231  }
   232  
   233  func (h *HTTPHandler) extractChannelID(req *http.Request, resp http.ResponseWriter) (string, error) {
   234  	channelID, ok := mux.Vars(req)[channelIDKey]
   235  	if !ok {
   236  		err := errors.New("missing channel ID")
   237  		h.sendResponseJsonError(resp, http.StatusInternalServerError, err)
   238  		return "", err
   239  	}
   240  
   241  	if err := configtx.ValidateChannelID(channelID); err != nil {
   242  		err = errors.Wrap(err, "invalid channel ID")
   243  		h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   244  		return "", err
   245  	}
   246  	return channelID, nil
   247  }
   248  
   249  func (h *HTTPHandler) sendJoinError(err error, resp http.ResponseWriter) {
   250  	h.logger.Debugf("Failed to JoinChannel: %s", err)
   251  	switch err {
   252  	case types.ErrSystemChannelExists:
   253  		// The client is trying to join an app-channel, but the system channel exists: only GET is allowed on app channels.
   254  		h.sendResponseNotAllowed(resp, errors.Wrap(err, "cannot join"), http.MethodGet)
   255  	case types.ErrChannelAlreadyExists:
   256  		// The client is trying to join an app-channel that exists, but the system channel does not;
   257  		// The client is trying to join the system-channel, and it exists. GET & DELETE are allowed on the channel.
   258  		h.sendResponseNotAllowed(resp, errors.Wrap(err, "cannot join"), http.MethodGet, http.MethodDelete)
   259  	case types.ErrAppChannelsAlreadyExists:
   260  		// The client is trying to join the system-channel that does not exist, but app channels exist.
   261  		h.sendResponseJsonError(resp, http.StatusForbidden, errors.Wrap(err, "cannot join"))
   262  	default:
   263  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot join"))
   264  	}
   265  }
   266  
   267  // Remove a channel
   268  // Expecting an optional query: "removeStorage=true" or "removeStorage=false".
   269  func (h *HTTPHandler) serveRemove(resp http.ResponseWriter, req *http.Request) {
   270  	_, err := negotiateContentType(req) // Only application/json responses for now
   271  	if err != nil {
   272  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   273  		return
   274  	}
   275  
   276  	channelID, err := h.extractChannelID(req, resp)
   277  	if err != nil {
   278  		return
   279  	}
   280  
   281  	removeStorage, err := h.extractRemoveStorageQuery(req, resp)
   282  	if err != nil {
   283  		return
   284  	}
   285  
   286  	err = h.registrar.RemoveChannel(channelID, removeStorage)
   287  	if err == nil {
   288  		h.logger.Debugf("Successfully removed channel: %s", channelID)
   289  		resp.WriteHeader(http.StatusNoContent)
   290  		return
   291  	}
   292  
   293  	h.logger.Debugf("Failed to remove channel: %s, err: %s", channelID, err)
   294  
   295  	switch err {
   296  	case types.ErrSystemChannelExists:
   297  		h.sendResponseNotAllowed(resp, errors.Wrap(err, "cannot remove"), http.MethodGet)
   298  	case types.ErrChannelNotExist:
   299  		h.sendResponseJsonError(resp, http.StatusNotFound, errors.Wrap(err, "cannot remove"))
   300  	default:
   301  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot remove"))
   302  	}
   303  }
   304  
   305  func (h *HTTPHandler) extractRemoveStorageQuery(req *http.Request, resp http.ResponseWriter) (bool, error) {
   306  	removeStorage := h.config.RemoveStorage
   307  	queryVal := req.URL.Query()
   308  	if len(queryVal) > 1 {
   309  		err := errors.New("cannot remove: too many query keys")
   310  		h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   311  		return false, err
   312  	}
   313  	if values, ok := queryVal[RemoveStorageQueryKey]; ok {
   314  		var err error
   315  		if len(values) != 1 {
   316  			err = errors.New("cannot remove: too many query parameters")
   317  			h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   318  			return false, err
   319  		}
   320  
   321  		removeStorage, err = strconv.ParseBool(values[0])
   322  		if err != nil {
   323  			h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot remove: invalid query parameter"))
   324  			return false, err
   325  		}
   326  	} else if len(queryVal) > 0 {
   327  		err := errors.New("cannot remove: invalid query key")
   328  		h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   329  		return false, err
   330  	}
   331  	return removeStorage, nil
   332  }
   333  
   334  func (h *HTTPHandler) serveBadContentType(resp http.ResponseWriter, req *http.Request) {
   335  	err := errors.Errorf("unsupported Content-Type: %s", req.Header.Values("Content-Type"))
   336  	h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   337  }
   338  
   339  func (h *HTTPHandler) serveNotAllowed(resp http.ResponseWriter, req *http.Request) {
   340  	err := errors.Errorf("invalid request method: %s", req.Method)
   341  
   342  	if _, ok := mux.Vars(req)[channelIDKey]; ok {
   343  		h.sendResponseNotAllowed(resp, err, http.MethodGet, http.MethodPost, http.MethodDelete)
   344  		return
   345  	}
   346  
   347  	h.sendResponseNotAllowed(resp, err, http.MethodGet)
   348  }
   349  
   350  func negotiateContentType(req *http.Request) (string, error) {
   351  	acceptReq := req.Header.Get("Accept")
   352  	if len(acceptReq) == 0 {
   353  		return "application/json", nil
   354  	}
   355  
   356  	options := strings.Split(acceptReq, ",")
   357  	for _, opt := range options {
   358  		if strings.Contains(opt, "application/json") ||
   359  			strings.Contains(opt, "application/*") ||
   360  			strings.Contains(opt, "*/*") {
   361  			return "application/json", nil
   362  		}
   363  	}
   364  
   365  	return "", errors.New("response Content-Type is application/json only")
   366  }
   367  
   368  func (h *HTTPHandler) sendResponseJsonError(resp http.ResponseWriter, code int, err error) {
   369  	encoder := json.NewEncoder(resp)
   370  	resp.Header().Set("Content-Type", "application/json")
   371  	resp.WriteHeader(code)
   372  	if err := encoder.Encode(&types.ErrorResponse{Error: err.Error()}); err != nil {
   373  		h.logger.Errorf("failed to encode error, err: %s", err)
   374  	}
   375  }
   376  
   377  func (h *HTTPHandler) sendResponseOK(resp http.ResponseWriter, content interface{}) {
   378  	encoder := json.NewEncoder(resp)
   379  	resp.Header().Set("Content-Type", "application/json")
   380  	resp.WriteHeader(http.StatusOK)
   381  	if err := encoder.Encode(content); err != nil {
   382  		h.logger.Errorf("failed to encode content, err: %s", err)
   383  	}
   384  }
   385  
   386  func (h *HTTPHandler) sendResponseCreated(resp http.ResponseWriter, location string, content interface{}) {
   387  	encoder := json.NewEncoder(resp)
   388  	resp.Header().Set("Location", location)
   389  	resp.Header().Set("Content-Type", "application/json")
   390  	resp.WriteHeader(http.StatusCreated)
   391  	if err := encoder.Encode(content); err != nil {
   392  		h.logger.Errorf("failed to encode content, err: %s", err)
   393  	}
   394  }
   395  
   396  func (h *HTTPHandler) sendResponseNotAllowed(resp http.ResponseWriter, err error, allow ...string) {
   397  	encoder := json.NewEncoder(resp)
   398  	allowVal := strings.Join(allow, ", ")
   399  	resp.Header().Set("Allow", allowVal)
   400  	resp.Header().Set("Content-Type", "application/json")
   401  	resp.WriteHeader(http.StatusMethodNotAllowed)
   402  	if err := encoder.Encode(&types.ErrorResponse{Error: err.Error()}); err != nil {
   403  		h.logger.Errorf("failed to encode error, err: %s", err)
   404  	}
   405  }