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 }