github.com/decred/politeia@v1.4.0/politeiad/v1.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  package main
     5  
     6  import (
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"errors"
    10  	"net/http"
    11  	"time"
    12  
    13  	v1 "github.com/decred/politeia/politeiad/api/v1"
    14  	"github.com/decred/politeia/politeiad/backend"
    15  	"github.com/decred/politeia/util"
    16  )
    17  
    18  func (p *politeia) getIdentity(w http.ResponseWriter, r *http.Request) {
    19  	var t v1.Identity
    20  	decoder := json.NewDecoder(r.Body)
    21  	if err := decoder.Decode(&t); err != nil {
    22  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
    23  		return
    24  	}
    25  
    26  	challenge, err := hex.DecodeString(t.Challenge)
    27  	if err != nil || len(challenge) != v1.ChallengeSize {
    28  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
    29  		return
    30  	}
    31  	response := p.identity.SignMessage(challenge)
    32  
    33  	reply := v1.IdentityReply{
    34  		PublicKey: hex.EncodeToString(p.identity.Public.Key[:]),
    35  		Response:  hex.EncodeToString(response[:]),
    36  	}
    37  
    38  	util.RespondWithJSON(w, http.StatusOK, reply)
    39  }
    40  
    41  func (p *politeia) newRecord(w http.ResponseWriter, r *http.Request) {
    42  	var t v1.NewRecord
    43  	decoder := json.NewDecoder(r.Body)
    44  	if err := decoder.Decode(&t); err != nil {
    45  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
    46  		return
    47  	}
    48  
    49  	challenge, err := hex.DecodeString(t.Challenge)
    50  	if err != nil || len(challenge) != v1.ChallengeSize {
    51  		log.Errorf("%v newRecord: invalid challenge", remoteAddr(r))
    52  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
    53  		return
    54  	}
    55  
    56  	log.Infof("New record submitted %v", remoteAddr(r))
    57  
    58  	md := convertFrontendMetadataStream(t.Metadata)
    59  	files := convertFrontendFiles(t.Files)
    60  	rm, err := p.backend.New(md, files)
    61  	if err != nil {
    62  		// Check for content error.
    63  		var contentErr backend.ContentVerificationError
    64  		if errors.As(err, &contentErr) {
    65  			log.Errorf("%v New record content error: %v",
    66  				remoteAddr(r), contentErr)
    67  			p.respondWithUserError(w, contentErr.ErrorCode,
    68  				contentErr.ErrorContext)
    69  			return
    70  		}
    71  
    72  		// Generic internal error.
    73  		errorCode := time.Now().Unix()
    74  		log.Errorf("%v New record error code %v: %v", remoteAddr(r),
    75  			errorCode, err)
    76  		p.respondWithServerError(w, errorCode, err)
    77  		return
    78  	}
    79  
    80  	// Prepare reply.
    81  	signature := p.identity.SignMessage([]byte(rm.Merkle + rm.Token))
    82  
    83  	response := p.identity.SignMessage(challenge)
    84  	reply := v1.NewRecordReply{
    85  		Response: hex.EncodeToString(response[:]),
    86  		CensorshipRecord: v1.CensorshipRecord{
    87  			Merkle:    rm.Merkle,
    88  			Token:     rm.Token,
    89  			Signature: hex.EncodeToString(signature[:]),
    90  		},
    91  	}
    92  
    93  	log.Infof("New record accepted %v: token %v", remoteAddr(r),
    94  		reply.CensorshipRecord.Token)
    95  
    96  	util.RespondWithJSON(w, http.StatusOK, reply)
    97  }
    98  
    99  func (p *politeia) updateRecord(w http.ResponseWriter, r *http.Request, vetted bool) {
   100  	cmd := "unvetted"
   101  	if vetted {
   102  		cmd = "vetted"
   103  	}
   104  
   105  	var t v1.UpdateRecord
   106  	decoder := json.NewDecoder(r.Body)
   107  	if err := decoder.Decode(&t); err != nil {
   108  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload,
   109  			nil)
   110  		return
   111  	}
   112  
   113  	challenge, err := hex.DecodeString(t.Challenge)
   114  	if err != nil || len(challenge) != v1.ChallengeSize {
   115  		log.Errorf("%v update %v record: invalid challenge",
   116  			remoteAddr(r), cmd)
   117  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   118  		return
   119  	}
   120  
   121  	// Validate token
   122  	token, err := util.ConvertStringToken(t.Token)
   123  	if err != nil {
   124  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload,
   125  			nil)
   126  		return
   127  	}
   128  
   129  	log.Infof("Update %v record submitted %v: %x", cmd, remoteAddr(r),
   130  		token)
   131  
   132  	var record *backend.Record
   133  	if vetted {
   134  		record, err = p.backend.UpdateVettedRecord(token,
   135  			convertFrontendMetadataStream(t.MDAppend),
   136  			convertFrontendMetadataStream(t.MDOverwrite),
   137  			convertFrontendFiles(t.FilesAdd), t.FilesDel)
   138  	} else {
   139  		record, err = p.backend.UpdateUnvettedRecord(token,
   140  			convertFrontendMetadataStream(t.MDAppend),
   141  			convertFrontendMetadataStream(t.MDOverwrite),
   142  			convertFrontendFiles(t.FilesAdd), t.FilesDel)
   143  	}
   144  	if err != nil {
   145  		if errors.Is(err, backend.ErrRecordFound) {
   146  			log.Errorf("%v update %v record found: %x",
   147  				remoteAddr(r), cmd, token)
   148  			p.respondWithUserError(w, v1.ErrorStatusRecordFound,
   149  				nil)
   150  			return
   151  		}
   152  		if errors.Is(err, backend.ErrRecordNotFound) {
   153  			log.Errorf("%v update %v record not found: %x",
   154  				remoteAddr(r), cmd, token)
   155  			p.respondWithUserError(w, v1.ErrorStatusRecordFound,
   156  				nil)
   157  			return
   158  		}
   159  		if errors.Is(err, backend.ErrNoChanges) {
   160  			log.Errorf("%v update %v record no changes: %x",
   161  				remoteAddr(r), cmd, token)
   162  			p.respondWithUserError(w, v1.ErrorStatusNoChanges, nil)
   163  			return
   164  		}
   165  		// Check for content error.
   166  		var contentErr backend.ContentVerificationError
   167  		if errors.As(err, &contentErr) {
   168  			log.Errorf("%v update %v record content error: %v",
   169  				remoteAddr(r), cmd, contentErr)
   170  			p.respondWithUserError(w, contentErr.ErrorCode,
   171  				contentErr.ErrorContext)
   172  			return
   173  		}
   174  
   175  		// Generic internal error.
   176  		errorCode := time.Now().Unix()
   177  		log.Errorf("%v Update %v record error code %v: %v",
   178  			remoteAddr(r), cmd, errorCode, err)
   179  		p.respondWithServerError(w, errorCode, err)
   180  		return
   181  	}
   182  
   183  	// Prepare reply.
   184  	response := p.identity.SignMessage(challenge)
   185  	reply := v1.UpdateRecordReply{
   186  		Response: hex.EncodeToString(response[:]),
   187  	}
   188  
   189  	log.Infof("Update %v record %v: token %v", cmd, remoteAddr(r),
   190  		record.RecordMetadata.Token)
   191  
   192  	util.RespondWithJSON(w, http.StatusOK, reply)
   193  }
   194  
   195  func (p *politeia) updateUnvetted(w http.ResponseWriter, r *http.Request) {
   196  	p.updateRecord(w, r, false)
   197  }
   198  
   199  func (p *politeia) updateVetted(w http.ResponseWriter, r *http.Request) {
   200  	p.updateRecord(w, r, true)
   201  }
   202  
   203  func (p *politeia) updateReadme(w http.ResponseWriter, r *http.Request) {
   204  	var t v1.UpdateReadme
   205  	decoder := json.NewDecoder(r.Body)
   206  	if err := decoder.Decode(&t); err != nil {
   207  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   208  		return
   209  	}
   210  
   211  	challenge, err := hex.DecodeString(t.Challenge)
   212  	if err != nil || len(challenge) != v1.ChallengeSize {
   213  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   214  		return
   215  	}
   216  
   217  	response := p.identity.SignMessage(challenge)
   218  
   219  	reply := v1.UpdateReadmeReply{
   220  		Response: hex.EncodeToString(response[:]),
   221  	}
   222  
   223  	err = p.backend.UpdateReadme(t.Content)
   224  	if err != nil {
   225  		errorCode := time.Now().Unix()
   226  		log.Errorf("Error updating readme: %v", err)
   227  		p.respondWithServerError(w, errorCode, err)
   228  		return
   229  	}
   230  
   231  	util.RespondWithJSON(w, http.StatusOK, reply)
   232  }
   233  
   234  func (p *politeia) getUnvetted(w http.ResponseWriter, r *http.Request) {
   235  	var t v1.GetUnvetted
   236  	decoder := json.NewDecoder(r.Body)
   237  	if err := decoder.Decode(&t); err != nil {
   238  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   239  		return
   240  	}
   241  
   242  	challenge, err := hex.DecodeString(t.Challenge)
   243  	if err != nil || len(challenge) != v1.ChallengeSize {
   244  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   245  		return
   246  	}
   247  	response := p.identity.SignMessage(challenge)
   248  
   249  	reply := v1.GetUnvettedReply{
   250  		Response: hex.EncodeToString(response[:]),
   251  	}
   252  
   253  	// Validate token
   254  	token, err := util.ConvertStringToken(t.Token)
   255  	if err != nil {
   256  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   257  		return
   258  	}
   259  
   260  	// Ask backend about the censorship token.
   261  	bpr, err := p.backend.GetUnvetted(token)
   262  	if errors.Is(err, backend.ErrRecordNotFound) {
   263  		reply.Record.Status = v1.RecordStatusNotFound
   264  		log.Errorf("Get unvetted record %v: token %v not found",
   265  			remoteAddr(r), t.Token)
   266  	} else if err != nil {
   267  		// Generic internal error.
   268  		errorCode := time.Now().Unix()
   269  		log.Errorf("%v Get unvetted record error code %v: %v",
   270  			remoteAddr(r), errorCode, err)
   271  
   272  		p.respondWithServerError(w, errorCode, err)
   273  		return
   274  	} else {
   275  		reply.Record = p.convertBackendRecord(*bpr)
   276  
   277  		// Double check record bits before sending them off
   278  		err := v1.Verify(p.identity.Public,
   279  			reply.Record.CensorshipRecord, reply.Record.Files)
   280  		if err != nil {
   281  			// Generic internal error.
   282  			errorCode := time.Now().Unix()
   283  			log.Errorf("%v Get unvetted record CORRUPTION "+
   284  				"error code %v: %v", remoteAddr(r), errorCode,
   285  				err)
   286  
   287  			p.respondWithServerError(w, errorCode, err)
   288  			return
   289  		}
   290  
   291  		log.Infof("Get unvetted record %v: token %v", remoteAddr(r),
   292  			t.Token)
   293  	}
   294  
   295  	util.RespondWithJSON(w, http.StatusOK, reply)
   296  }
   297  
   298  func (p *politeia) getVetted(w http.ResponseWriter, r *http.Request) {
   299  	var t v1.GetVetted
   300  	decoder := json.NewDecoder(r.Body)
   301  	if err := decoder.Decode(&t); err != nil {
   302  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   303  		return
   304  	}
   305  
   306  	challenge, err := hex.DecodeString(t.Challenge)
   307  	if err != nil || len(challenge) != v1.ChallengeSize {
   308  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   309  		return
   310  	}
   311  	response := p.identity.SignMessage(challenge)
   312  
   313  	reply := v1.GetVettedReply{
   314  		Response: hex.EncodeToString(response[:]),
   315  	}
   316  
   317  	// Validate token
   318  	token, err := util.ConvertStringToken(t.Token)
   319  	if err != nil {
   320  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   321  		return
   322  	}
   323  
   324  	// Ask backend about the censorship token.
   325  	bpr, err := p.backend.GetVetted(token, t.Version)
   326  	if errors.Is(err, backend.ErrRecordNotFound) {
   327  		reply.Record.Status = v1.RecordStatusNotFound
   328  		log.Errorf("Get vetted record %v: token %v not found",
   329  			remoteAddr(r), t.Token)
   330  	} else if err != nil {
   331  		// Generic internal error.
   332  		errorCode := time.Now().Unix()
   333  		log.Errorf("%v Get vetted record error code %v: %v",
   334  			remoteAddr(r), errorCode, err)
   335  
   336  		p.respondWithServerError(w, errorCode, err)
   337  		return
   338  	} else {
   339  		reply.Record = p.convertBackendRecord(*bpr)
   340  
   341  		// Double check record bits before sending them off
   342  		err := v1.Verify(p.identity.Public,
   343  			reply.Record.CensorshipRecord, reply.Record.Files)
   344  		if err != nil {
   345  			// Generic internal error.
   346  			errorCode := time.Now().Unix()
   347  			log.Errorf("%v Get vetted record CORRUPTION "+
   348  				"error code %v: %v", remoteAddr(r), errorCode,
   349  				err)
   350  
   351  			p.respondWithServerError(w, errorCode, err)
   352  			return
   353  		}
   354  		log.Infof("Get vetted record %v: token %v", remoteAddr(r),
   355  			t.Token)
   356  	}
   357  
   358  	util.RespondWithJSON(w, http.StatusOK, reply)
   359  }
   360  
   361  func (p *politeia) inventory(w http.ResponseWriter, r *http.Request) {
   362  	var i v1.Inventory
   363  	decoder := json.NewDecoder(r.Body)
   364  	if err := decoder.Decode(&i); err != nil {
   365  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   366  		return
   367  	}
   368  
   369  	challenge, err := hex.DecodeString(i.Challenge)
   370  	if err != nil || len(challenge) != v1.ChallengeSize {
   371  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   372  		return
   373  	}
   374  	response := p.identity.SignMessage(challenge)
   375  
   376  	reply := v1.InventoryReply{
   377  		Response: hex.EncodeToString(response[:]),
   378  	}
   379  
   380  	// Ask backend for inventory
   381  	prs, brs, err := p.backend.Inventory(i.VettedCount, i.VettedStart, i.BranchesCount,
   382  		i.IncludeFiles, i.AllVersions)
   383  	if err != nil {
   384  		// Generic internal error.
   385  		errorCode := time.Now().Unix()
   386  		log.Errorf("%v Inventory error code %v: %v", remoteAddr(r),
   387  			errorCode, err)
   388  
   389  		p.respondWithServerError(w, errorCode, err)
   390  		return
   391  	}
   392  
   393  	// Convert backend records
   394  	vetted := make([]v1.Record, 0, len(prs))
   395  	for _, v := range prs {
   396  		vetted = append(vetted, p.convertBackendRecord(v))
   397  	}
   398  	reply.Vetted = vetted
   399  
   400  	// Convert branches
   401  	unvetted := make([]v1.Record, 0, len(brs))
   402  	for _, v := range brs {
   403  		unvetted = append(unvetted, p.convertBackendRecord(v))
   404  	}
   405  	reply.Branches = unvetted
   406  
   407  	util.RespondWithJSON(w, http.StatusOK, reply)
   408  }
   409  
   410  func (p *politeia) setVettedStatus(w http.ResponseWriter, r *http.Request) {
   411  	var t v1.SetVettedStatus
   412  	decoder := json.NewDecoder(r.Body)
   413  	if err := decoder.Decode(&t); err != nil {
   414  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   415  		return
   416  	}
   417  
   418  	challenge, err := hex.DecodeString(t.Challenge)
   419  	if err != nil || len(challenge) != v1.ChallengeSize {
   420  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   421  		return
   422  	}
   423  	response := p.identity.SignMessage(challenge)
   424  
   425  	// Validate token
   426  	token, err := util.ConvertStringToken(t.Token)
   427  	if err != nil {
   428  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   429  		return
   430  	}
   431  
   432  	// Ask backend to update  status
   433  	record, err := p.backend.SetVettedStatus(token,
   434  		convertFrontendStatus(t.Status),
   435  		convertFrontendMetadataStream(t.MDAppend),
   436  		convertFrontendMetadataStream(t.MDOverwrite))
   437  	if err != nil {
   438  		// Check for specific errors
   439  		if errors.Is(err, backend.ErrRecordNotFound) {
   440  			log.Errorf("%v updateStatus record not "+
   441  				"found: %x", remoteAddr(r), token)
   442  			p.respondWithUserError(w, v1.ErrorStatusRecordFound,
   443  				nil)
   444  			return
   445  		}
   446  		var serr backend.StateTransitionError
   447  		if errors.As(err, &serr) {
   448  			log.Errorf("%v %v %v", remoteAddr(r), t.Token, err)
   449  			p.respondWithUserError(w, v1.ErrorStatusInvalidRecordStatusTransition, nil)
   450  			return
   451  		}
   452  		// Generic internal error.
   453  		errorCode := time.Now().Unix()
   454  		log.Errorf("%v Set status error code %v: %v",
   455  			remoteAddr(r), errorCode, err)
   456  
   457  		p.respondWithServerError(w, errorCode, err)
   458  		return
   459  	}
   460  
   461  	// Prepare reply.
   462  	reply := v1.SetVettedStatusReply{
   463  		Response: hex.EncodeToString(response[:]),
   464  	}
   465  
   466  	s := convertBackendStatus(record.RecordMetadata.Status)
   467  	log.Infof("Set vetted record status %v: token %v status %v",
   468  		remoteAddr(r), t.Token, v1.RecordStatus[s])
   469  
   470  	util.RespondWithJSON(w, http.StatusOK, reply)
   471  }
   472  
   473  func (p *politeia) setUnvettedStatus(w http.ResponseWriter, r *http.Request) {
   474  	var t v1.SetUnvettedStatus
   475  	decoder := json.NewDecoder(r.Body)
   476  	if err := decoder.Decode(&t); err != nil {
   477  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   478  		return
   479  	}
   480  
   481  	challenge, err := hex.DecodeString(t.Challenge)
   482  	if err != nil || len(challenge) != v1.ChallengeSize {
   483  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   484  		return
   485  	}
   486  	response := p.identity.SignMessage(challenge)
   487  
   488  	// Validate token
   489  	token, err := util.ConvertStringToken(t.Token)
   490  	if err != nil {
   491  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   492  		return
   493  	}
   494  
   495  	// Ask backend to update unvetted status
   496  	record, err := p.backend.SetUnvettedStatus(token,
   497  		convertFrontendStatus(t.Status),
   498  		convertFrontendMetadataStream(t.MDAppend),
   499  		convertFrontendMetadataStream(t.MDOverwrite))
   500  	if err != nil {
   501  		// Check for specific errors
   502  		if errors.Is(err, backend.ErrRecordNotFound) {
   503  			log.Errorf("%v updateUnvettedStatus record not "+
   504  				"found: %x", remoteAddr(r), token)
   505  			p.respondWithUserError(w, v1.ErrorStatusRecordFound,
   506  				nil)
   507  			return
   508  		}
   509  		var serr backend.StateTransitionError
   510  		if errors.As(err, &serr) {
   511  			log.Errorf("%v %v %v", remoteAddr(r), t.Token, err)
   512  			p.respondWithUserError(w, v1.ErrorStatusInvalidRecordStatusTransition, nil)
   513  			return
   514  		}
   515  		// Generic internal error.
   516  		errorCode := time.Now().Unix()
   517  		log.Errorf("%v Set unvetted status error code %v: %v",
   518  			remoteAddr(r), errorCode, err)
   519  
   520  		p.respondWithServerError(w, errorCode, err)
   521  		return
   522  	}
   523  
   524  	// Prepare reply.
   525  	reply := v1.SetUnvettedStatusReply{
   526  		Response: hex.EncodeToString(response[:]),
   527  	}
   528  
   529  	s := convertBackendStatus(record.RecordMetadata.Status)
   530  	log.Infof("Set unvetted record status %v: token %v status %v",
   531  		remoteAddr(r), t.Token, v1.RecordStatus[s])
   532  
   533  	util.RespondWithJSON(w, http.StatusOK, reply)
   534  }
   535  
   536  func (p *politeia) updateVettedMetadata(w http.ResponseWriter, r *http.Request) {
   537  	var t v1.UpdateVettedMetadata
   538  	decoder := json.NewDecoder(r.Body)
   539  	if err := decoder.Decode(&t); err != nil {
   540  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   541  		return
   542  	}
   543  
   544  	challenge, err := hex.DecodeString(t.Challenge)
   545  	if err != nil || len(challenge) != v1.ChallengeSize {
   546  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   547  		return
   548  	}
   549  	response := p.identity.SignMessage(challenge)
   550  
   551  	// Validate token
   552  	token, err := util.ConvertStringToken(t.Token)
   553  	if err != nil {
   554  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload, nil)
   555  		return
   556  	}
   557  
   558  	log.Infof("Update vetted metadata submitted %v: %x", remoteAddr(r),
   559  		token)
   560  
   561  	err = p.backend.UpdateVettedMetadata(token,
   562  		convertFrontendMetadataStream(t.MDAppend),
   563  		convertFrontendMetadataStream(t.MDOverwrite))
   564  	if err != nil {
   565  		if errors.Is(err, backend.ErrNoChanges) {
   566  			log.Errorf("%v update vetted metadata no changes: %x",
   567  				remoteAddr(r), token)
   568  			p.respondWithUserError(w, v1.ErrorStatusNoChanges, nil)
   569  			return
   570  		}
   571  		// Check for content error.
   572  		var contentErr backend.ContentVerificationError
   573  		if errors.As(err, &contentErr) {
   574  			log.Errorf("%v update vetted metadata content error: %v",
   575  				remoteAddr(r), contentErr)
   576  			p.respondWithUserError(w, contentErr.ErrorCode,
   577  				contentErr.ErrorContext)
   578  			return
   579  		}
   580  
   581  		// Generic internal error.
   582  		errorCode := time.Now().Unix()
   583  		log.Errorf("%v Update vetted metadata error code %v: %v",
   584  			remoteAddr(r), errorCode, err)
   585  		p.respondWithServerError(w, errorCode, err)
   586  		return
   587  	}
   588  
   589  	// Reply
   590  	reply := v1.UpdateVettedMetadataReply{
   591  		Response: hex.EncodeToString(response[:]),
   592  	}
   593  
   594  	log.Infof("Update vetted metadata %v: token %x", remoteAddr(r), token)
   595  
   596  	util.RespondWithJSON(w, http.StatusOK, reply)
   597  }
   598  
   599  func (p *politeia) pluginInventory(w http.ResponseWriter, r *http.Request) {
   600  	var pi v1.PluginInventory
   601  	decoder := json.NewDecoder(r.Body)
   602  	if err := decoder.Decode(&pi); err != nil {
   603  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload,
   604  			nil)
   605  		return
   606  	}
   607  
   608  	challenge, err := hex.DecodeString(pi.Challenge)
   609  	if err != nil || len(challenge) != v1.ChallengeSize {
   610  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   611  		return
   612  	}
   613  	response := p.identity.SignMessage(challenge)
   614  
   615  	reply := v1.PluginInventoryReply{
   616  		Response: hex.EncodeToString(response[:]),
   617  	}
   618  
   619  	plugins, err := p.backend.GetPlugins()
   620  	if err != nil {
   621  		// Generic internal error.
   622  		errorCode := time.Now().Unix()
   623  		log.Errorf("%v Get plugins error code %v: %v",
   624  			remoteAddr(r), errorCode, err)
   625  		p.respondWithServerError(w, errorCode, err)
   626  		return
   627  	}
   628  	for _, v := range plugins {
   629  		reply.Plugins = append(reply.Plugins, convertBackendPlugin(v))
   630  	}
   631  
   632  	util.RespondWithJSON(w, http.StatusOK, reply)
   633  }
   634  
   635  func (p *politeia) pluginCommand(w http.ResponseWriter, r *http.Request) {
   636  	var pc v1.PluginCommand
   637  	decoder := json.NewDecoder(r.Body)
   638  	if err := decoder.Decode(&pc); err != nil {
   639  		p.respondWithUserError(w, v1.ErrorStatusInvalidRequestPayload,
   640  			nil)
   641  		return
   642  	}
   643  
   644  	challenge, err := hex.DecodeString(pc.Challenge)
   645  	if err != nil || len(challenge) != v1.ChallengeSize {
   646  		p.respondWithUserError(w, v1.ErrorStatusInvalidChallenge, nil)
   647  		return
   648  	}
   649  
   650  	cid, payload, err := p.backend.Plugin(pc.Command, pc.Payload)
   651  	if err != nil {
   652  		// Generic internal error.
   653  		errorCode := time.Now().Unix()
   654  		log.Errorf("%v %v: backend plugin failed with "+
   655  			"command:%v payload:%v err:%v", remoteAddr(r),
   656  			errorCode, pc.Command, pc.Payload, err)
   657  		p.respondWithServerError(w, errorCode, err)
   658  		return
   659  	}
   660  
   661  	response := p.identity.SignMessage(challenge)
   662  	reply := v1.PluginCommandReply{
   663  		Response:  hex.EncodeToString(response[:]),
   664  		ID:        pc.ID,
   665  		Command:   cid,
   666  		CommandID: pc.CommandID,
   667  		Payload:   payload,
   668  	}
   669  
   670  	util.RespondWithJSON(w, http.StatusOK, reply)
   671  }
   672  
   673  func convertBackendPluginSetting(bpi backend.PluginSetting) v1.PluginSetting {
   674  	return v1.PluginSetting{
   675  		Key:   bpi.Key,
   676  		Value: bpi.Value,
   677  	}
   678  }
   679  
   680  func convertBackendPlugin(bpi backend.Plugin) v1.Plugin {
   681  	p := v1.Plugin{
   682  		ID: bpi.ID,
   683  	}
   684  	for _, v := range bpi.Settings {
   685  		p.Settings = append(p.Settings, convertBackendPluginSetting(v))
   686  	}
   687  
   688  	return p
   689  }
   690  
   691  // convertBackendMetadataStream converts a backend metadata stream to an API
   692  // metadata stream.
   693  func convertBackendMetadataStream(mds backend.MetadataStream) v1.MetadataStream {
   694  	return v1.MetadataStream{
   695  		ID:      mds.ID,
   696  		Payload: mds.Payload,
   697  	}
   698  }
   699  
   700  // convertBackendStatus converts a backend MDStatus to an API status.
   701  func convertBackendStatus(status backend.MDStatusT) v1.RecordStatusT {
   702  	s := v1.RecordStatusInvalid
   703  	switch status {
   704  	case backend.MDStatusInvalid:
   705  		s = v1.RecordStatusInvalid
   706  	case backend.MDStatusUnvetted:
   707  		s = v1.RecordStatusNotReviewed
   708  	case backend.MDStatusVetted:
   709  		s = v1.RecordStatusPublic
   710  	case backend.MDStatusCensored:
   711  		s = v1.RecordStatusCensored
   712  	case backend.MDStatusIterationUnvetted:
   713  		s = v1.RecordStatusUnreviewedChanges
   714  	case backend.MDStatusArchived:
   715  		s = v1.RecordStatusArchived
   716  	}
   717  	return s
   718  }
   719  
   720  // convertFrontendStatus convert an API status to a backend MDStatus.
   721  func convertFrontendStatus(status v1.RecordStatusT) backend.MDStatusT {
   722  	s := backend.MDStatusInvalid
   723  	switch status {
   724  	case v1.RecordStatusInvalid:
   725  		s = backend.MDStatusInvalid
   726  	case v1.RecordStatusNotReviewed:
   727  		s = backend.MDStatusUnvetted
   728  	case v1.RecordStatusPublic:
   729  		s = backend.MDStatusVetted
   730  	case v1.RecordStatusCensored:
   731  		s = backend.MDStatusCensored
   732  	case v1.RecordStatusArchived:
   733  		s = backend.MDStatusArchived
   734  	}
   735  	return s
   736  }
   737  
   738  func convertFrontendFiles(f []v1.File) []backend.File {
   739  	files := make([]backend.File, 0, len(f))
   740  	for _, v := range f {
   741  		files = append(files, backend.File{
   742  			Name:    v.Name,
   743  			MIME:    v.MIME,
   744  			Digest:  v.Digest,
   745  			Payload: v.Payload,
   746  		})
   747  	}
   748  	return files
   749  }
   750  
   751  func convertFrontendMetadataStream(mds []v1.MetadataStream) []backend.MetadataStream {
   752  	m := make([]backend.MetadataStream, 0, len(mds))
   753  	for _, v := range mds {
   754  		m = append(m, backend.MetadataStream{
   755  			ID:      v.ID,
   756  			Payload: v.Payload,
   757  		})
   758  	}
   759  	return m
   760  }
   761  
   762  func (p *politeia) convertBackendRecord(br backend.Record) v1.Record {
   763  	rm := br.RecordMetadata
   764  
   765  	// Calculate signature
   766  	signature := p.identity.SignMessage([]byte(rm.Merkle + rm.Token))
   767  
   768  	// Convert MetadataStream
   769  	md := make([]v1.MetadataStream, 0, len(br.Metadata))
   770  	for k := range br.Metadata {
   771  		md = append(md, convertBackendMetadataStream(br.Metadata[k]))
   772  	}
   773  
   774  	// Convert record
   775  	pr := v1.Record{
   776  		Status:    convertBackendStatus(rm.Status),
   777  		Timestamp: rm.Timestamp,
   778  		CensorshipRecord: v1.CensorshipRecord{
   779  			Merkle:    rm.Merkle,
   780  			Token:     rm.Token,
   781  			Signature: hex.EncodeToString(signature[:]),
   782  		},
   783  		Version:  br.Version,
   784  		Metadata: md,
   785  	}
   786  	pr.Files = make([]v1.File, 0, len(br.Files))
   787  	for _, v := range br.Files {
   788  		pr.Files = append(pr.Files,
   789  			v1.File{
   790  				Name:    v.Name,
   791  				MIME:    v.MIME,
   792  				Digest:  v.Digest,
   793  				Payload: v.Payload,
   794  			})
   795  	}
   796  
   797  	return pr
   798  }