diff --git a/contracts/evoting/controller/action.go b/contracts/evoting/controller/action.go index b62c03f1c..2d262ffbc 100644 --- a/contracts/evoting/controller/action.go +++ b/contracts/evoting/controller/action.go @@ -48,9 +48,12 @@ const ( contentType = "application/json" formPath = "/evoting/forms" // FormPathSlash is the path to the form with a trailing slash - FormPathSlash = formPath + "/" - formIDPath = FormPathSlash + "{formID}" - transactionSlash = "/evoting/transactions/" + FormPathSlash = formPath + "/" + formIDPath = FormPathSlash + "{formID}" + transactionSlash = "/evoting/transactions/" + + evotingPathSlash = "/evoting/" + transactionPath = transactionSlash + "{token}" unexpectedStatus = "unexpected status: %s, body: %s" failRetrieveDecryption = "failed to retrieve decryption key: %v" @@ -169,12 +172,19 @@ func (a *RegisterAction) Execute(ctx node.Context) error { return xerrors.Errorf("failed to unmarshal proxy key: %v", err) } - transactionManager := txnmanager.NewTransactionManager(mngr, p, sjson.NewContext(), proxykey, blocks, signer) + transactionManager := txnmanager.NewTransactionManager(mngr, p, sjson.NewContext(), proxykey, blocks, signer, validation) ep := eproxy.NewForm(ordering, p, sjson.NewContext(), formFac, proxykey, transactionManager) router := mux.NewRouter() + router.HandleFunc(evotingPathSlash+"addadmin", ep.AddAdmin).Methods("POST") + router.HandleFunc(evotingPathSlash+"removeadmin", ep.RemoveAdmin).Methods("POST") + router.HandleFunc(evotingPathSlash+"adminlist", ep.AdminList).Methods("GET") + router.HandleFunc(formIDPath+"/addowner", ep.AddOwnerToForm).Methods("POST") + router.HandleFunc(formIDPath+"/removeowner", ep.RemoveOwnerToForm).Methods("POST") + router.HandleFunc(formIDPath+"/addvoter", ep.AddVoterToForm).Methods("POST") + router.HandleFunc(formIDPath+"/removevoter", ep.RemoveVoterToForm).Methods("POST") router.HandleFunc(formPath, ep.NewForm).Methods("POST") router.HandleFunc(formPath, ep.Forms).Methods("GET") router.HandleFunc(formPath, eproxy.AllowCORS).Methods("OPTIONS") @@ -188,6 +198,7 @@ func (a *RegisterAction) Execute(ctx node.Context) error { router.NotFoundHandler = http.HandlerFunc(eproxy.NotFoundHandler) router.MethodNotAllowedHandler = http.HandlerFunc(eproxy.NotAllowedHandler) + proxy.RegisterHandler(evotingPathSlash, router.ServeHTTP) proxy.RegisterHandler(formPath, router.ServeHTTP) proxy.RegisterHandler(FormPathSlash, router.ServeHTTP) proxy.RegisterHandler(transactionSlash, router.ServeHTTP) diff --git a/proxy/election.go b/proxy/election.go index caf019185..d60499725 100644 --- a/proxy/election.go +++ b/proxy/election.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "sync" "github.com/c4dt/d-voting/contracts/evoting" @@ -19,7 +20,6 @@ import ( "go.dedis.ch/dela/core/txn/pool" "go.dedis.ch/dela/serde" "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/sign/schnorr" "golang.org/x/xerrors" ) @@ -37,14 +37,23 @@ func NewForm(srv ordering.Service, p pool.Pool, logger := dela.Logger.With().Timestamp().Str("role", "evoting-proxy").Logger() + // Compute the ID of the admin list id + // We need it to filter the send list of form + h := sha256.New() + h.Write([]byte(evoting.AdminListId)) + adminListIDBuf := h.Sum(nil) + adminListID := hex.EncodeToString(adminListIDBuf) + return &form{ logger: logger, orderingSvc: srv, context: ctx, formFac: fac, + adminFac: types.AdminListFactory{}, mngr: txnManaxer, pool: p, pk: pk, + adminListID: adminListID, } } @@ -58,13 +67,15 @@ type form struct { logger zerolog.Logger context serde.Context formFac serde.Factory + adminFac serde.Factory mngr txnmanager.Manager pool pool.Pool pk kyber.Point + adminListID string } // NewForm implements proxy.Proxy -func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { +func (form *form) NewForm(w http.ResponseWriter, r *http.Request) { var req ptypes.CreateFormRequest // get the signed request @@ -75,7 +86,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // get the request and verify the signature - err = signed.GetAndVerify(h.pk, &req) + err = signed.GetAndVerify(form.pk, &req) if err != nil { InternalError(w, r, getSignedErr(err), nil) return @@ -87,7 +98,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // serialize the transaction - data, err := createForm.Serialize(h.context) + data, err := createForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CreateFormTransaction: "+err.Error(), http.StatusInternalServerError) @@ -95,7 +106,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // create the transaction and add it to the pool - txnID, blockIdx, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCreateForm, evoting.FormArg, data) + txnID, blockIdx, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCreateForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return @@ -107,7 +118,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { formID := hash.Sum(nil) // create it to get the token - transactionClientInfo, err := h.mngr.CreateTransactionResult(txnID, blockIdx, txnmanager.UnknownTransactionStatus) + transactionClientInfo, err := form.mngr.CreateTransactionResult(txnID, blockIdx, txnmanager.UnknownTransactionStatus) if err != nil { http.Error(w, "failed to create transaction info: "+err.Error(), http.StatusInternalServerError) return @@ -126,7 +137,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // NewFormVote implements proxy.Proxy -func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { +func (form *form) NewFormVote(w http.ResponseWriter, r *http.Request) { var req ptypes.CastVoteRequest // get the signed request @@ -137,7 +148,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // get the request and verify the signature - err = signed.GetAndVerify(h.pk, &req) + err = signed.GetAndVerify(form.pk, &req) if err != nil { InternalError(w, r, getSignedErr(err), nil) return @@ -153,7 +164,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { formID := vars["formID"] - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { http.Error(w, "failed to get form metadata", http.StatusNotFound) return @@ -198,7 +209,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // serialize the vote - data, err := castVote.Serialize(h.context) + data, err := castVote.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CastVoteTransaction: "+err.Error(), http.StatusInternalServerError) @@ -206,15 +217,15 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCastVote, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCastVote, evoting.FormArg, data) if err != nil { - h.logger.Err(err).Msg("failed to submit txn") + form.logger.Err(err).Msg("failed to submit txn") http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's information - err = h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + err = form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) if err != nil { http.Error(w, "couldn't send transaction info: "+err.Error(), http.StatusInternalServerError) return @@ -222,7 +233,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // EditForm implements proxy.Proxy -func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { +func (form *form) EditForm(w http.ResponseWriter, r *http.Request) { var req ptypes.UpdateFormRequest // get the signed request @@ -233,7 +244,7 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { } // get the request and verify the signature - err = signed.GetAndVerify(h.pk, &req) + err = signed.GetAndVerify(form.pk, &req) if err != nil { InternalError(w, r, getSignedErr(err), nil) return @@ -248,9 +259,8 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { } formID := vars["formID"] - userID := vars["userID"] - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { http.Error(w, "failed to get form metadata", http.StatusNotFound) return @@ -264,13 +274,13 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { switch req.Action { case "open": - h.openForm(formID, userID, w, r) + form.openForm(formID, req.UserID, w, r) case "close": - h.closeForm(formID, userID, w, r) + form.closeForm(formID, req.UserID, w, r) case "combineShares": - h.combineShares(formID, userID, w, r) + form.combineShares(formID, req.UserID, w, r) case "cancel": - h.cancelForm(formID, userID, w, r) + form.cancelForm(formID, req.UserID, w, r) default: BadRequestError(w, r, xerrors.Errorf("invalid action: %s", req.Action), nil) return @@ -279,14 +289,14 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { // openForm allows opening a form, which sets the public key based on // the DKG actor. -func (h *form) openForm(formID string, userID string, w http.ResponseWriter, r *http.Request) { +func (form *form) openForm(formID string, userID string, w http.ResponseWriter, r *http.Request) { openForm := types.OpenForm{ FormID: formID, UserID: userID, } // serialize the transaction - data, err := openForm.Serialize(h.context) + data, err := openForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal OpenFormTransaction: "+err.Error(), http.StatusInternalServerError) @@ -294,18 +304,18 @@ func (h *form) openForm(formID string, userID string, w http.ResponseWriter, r * } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdOpenForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdOpenForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // closeForm closes a form. -func (h *form) closeForm(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { +func (form *form) closeForm(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { closeForm := types.CloseForm{ FormID: formIDHex, @@ -313,7 +323,7 @@ func (h *form) closeForm(formIDHex string, userID string, w http.ResponseWriter, } // serialize the transaction - data, err := closeForm.Serialize(h.context) + data, err := closeForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CloseFormTransaction: "+err.Error(), http.StatusInternalServerError) @@ -321,27 +331,27 @@ func (h *form) closeForm(formIDHex string, userID string, w http.ResponseWriter, } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCloseForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCloseForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // combineShares decrypts the shuffled ballots in a form. -func (h *form) combineShares(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { +func (form *form) combineShares(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { - form, err := types.FormFromStore(h.context, h.formFac, formIDHex, h.orderingSvc.GetStore()) + formFromStore, err := types.FormFromStore(form.context, form.formFac, formIDHex, form.orderingSvc.GetStore()) if err != nil { http.Error(w, "failed to get form: "+err.Error(), http.StatusInternalServerError) return } - if form.Status != types.PubSharesSubmitted { + if formFromStore.Status != types.PubSharesSubmitted { http.Error(w, "the submission of public shares must be over!", http.StatusUnauthorized) return @@ -353,7 +363,7 @@ func (h *form) combineShares(formIDHex string, userID string, w http.ResponseWri } // serialize the transaction - data, err := decryptBallots.Serialize(h.context) + data, err := decryptBallots.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal decryptBallots: "+err.Error(), http.StatusInternalServerError) @@ -361,18 +371,18 @@ func (h *form) combineShares(formIDHex string, userID string, w http.ResponseWri } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCombineShares, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCombineShares, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // cancelForm cancels a form. -func (h *form) cancelForm(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { +func (form *form) cancelForm(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { cancelForm := types.CancelForm{ FormID: formIDHex, @@ -380,7 +390,7 @@ func (h *form) cancelForm(formIDHex string, userID string, w http.ResponseWriter } // serialize the transaction - data, err := cancelForm.Serialize(h.context) + data, err := cancelForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CancelForm: "+err.Error(), http.StatusInternalServerError) @@ -388,19 +398,19 @@ func (h *form) cancelForm(formIDHex string, userID string, w http.ResponseWriter } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCancelForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCancelForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // Form implements proxy.Proxy. The request should not be signed because it // is fetching public data. -func (h *form) Form(w http.ResponseWriter, r *http.Request) { +func (form *form) Form(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") @@ -415,7 +425,7 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { formID := vars["formID"] // get the form - form, err := types.FormFromStore(h.context, h.formFac, formID, h.orderingSvc.GetStore()) + formFromStore, err := types.FormFromStore(form.context, form.formFac, formID, form.orderingSvc.GetStore()) if err != nil { http.Error(w, xerrors.Errorf("failed to get form: %v", err).Error(), http.StatusInternalServerError) return @@ -424,8 +434,8 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { var pubkeyBuf []byte // get the public key - if form.Pubkey != nil { - pubkeyBuf, err = form.Pubkey.MarshalBinary() + if formFromStore.Pubkey != nil { + pubkeyBuf, err = formFromStore.Pubkey.MarshalBinary() if err != nil { http.Error(w, "failed to marshal pubkey: "+err.Error(), http.StatusInternalServerError) @@ -433,14 +443,14 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { } } - roster := make([]string, 0, form.Roster.Len()) + roster := make([]string, 0, formFromStore.Roster.Len()) - iter := form.Roster.AddressIterator() + iter := formFromStore.Roster.AddressIterator() for iter.HasNext() { roster = append(roster, iter.GetNext().String()) } - suff, err := form.Suffragia(h.context, h.orderingSvc.GetStore()) + suff, err := formFromStore.Suffragia(form.context, form.orderingSvc.GetStore()) if err != nil { http.Error(w, "couldn't get ballots: "+err.Error(), http.StatusInternalServerError) @@ -448,14 +458,14 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { } response := ptypes.GetFormResponse{ - FormID: string(form.FormID), - Configuration: form.Configuration, - Status: uint16(form.Status), + FormID: string(formFromStore.FormID), + Configuration: formFromStore.Configuration, + Status: uint16(formFromStore.Status), Pubkey: hex.EncodeToString(pubkeyBuf), - Result: form.DecryptedBallots, + Result: formFromStore.DecryptedBallots, Roster: roster, - ChunksPerBallot: form.ChunksPerBallot(), - BallotSize: form.BallotSize, + ChunksPerBallot: formFromStore.ChunksPerBallot(), + BallotSize: formFromStore.BallotSize, Voters: suff.VoterIDs, } @@ -465,11 +475,11 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { // Forms implements proxy.Proxy. The request should not be signed because it // is fecthing public data. -func (h *form) Forms(w http.ResponseWriter, r *http.Request) { +func (form *form) Forms(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { InternalError(w, r, xerrors.Errorf("failed to get form metadata: %v", err), nil) return @@ -479,30 +489,32 @@ func (h *form) Forms(w http.ResponseWriter, r *http.Request) { // get the forms for i, id := range elecMD.FormsIDs { - form, err := types.FormFromStore(h.context, h.formFac, id, h.orderingSvc.GetStore()) - if err != nil { - InternalError(w, r, xerrors.Errorf("failed to get form: %v", err), nil) - return - } - - var pubkeyBuf []byte - - if form.Pubkey != nil { - pubkeyBuf, err = form.Pubkey.MarshalBinary() + if id != form.adminListID { + form, err := types.FormFromStore(form.context, form.formFac, id, form.orderingSvc.GetStore()) if err != nil { - InternalError(w, r, xerrors.Errorf("failed to marshal pubkey: %v", err), nil) + InternalError(w, r, xerrors.Errorf("failed to get form: %v", err), nil) return } - } - info := ptypes.LightForm{ - FormID: string(form.FormID), - Title: form.Configuration.Title, - Status: uint16(form.Status), - Pubkey: hex.EncodeToString(pubkeyBuf), - } + var pubkeyBuf []byte + + if form.Pubkey != nil { + pubkeyBuf, err = form.Pubkey.MarshalBinary() + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal pubkey: %v", err), nil) + return + } + } + + info := ptypes.LightForm{ + FormID: string(form.FormID), + Title: form.Configuration.Title, + Status: uint16(form.Status), + Pubkey: hex.EncodeToString(pubkeyBuf), + } - allFormsInfo[i] = info + allFormsInfo[i] = info + } } response := ptypes.GetFormsResponse{Forms: allFormsInfo} @@ -512,7 +524,9 @@ func (h *form) Forms(w http.ResponseWriter, r *http.Request) { } // DeleteForm implements proxy.Proxy -func (h *form) DeleteForm(w http.ResponseWriter, r *http.Request) { +func (form *form) DeleteForm(w http.ResponseWriter, r *http.Request) { + var req ptypes.UpdateFormRequest + vars := mux.Vars(r) // check if the formID is valid @@ -522,9 +536,8 @@ func (h *form) DeleteForm(w http.ResponseWriter, r *http.Request) { } formID := vars["formID"] - userID := vars["userID"] - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { http.Error(w, "failed to get form metadata", http.StatusNotFound) return @@ -535,50 +548,270 @@ func (h *form) DeleteForm(w http.ResponseWriter, r *http.Request) { http.Error(w, "the form does not exist", http.StatusNotFound) return } - - // auth should contain the hex-encoded signature on the hex-encoded form - // ID - auth := r.Header.Get("Authorization") - - signature, err := hex.DecodeString(auth) + + // get the signed request + signed, err := ptypes.NewSignedRequest(r.Body) if err != nil { - BadRequestError(w, r, xerrors.Errorf("failed to decode auth: %v", err), nil) + InternalError(w, r, newSignedErr(err), nil) return } - // check if the signature is valid - err = schnorr.Verify(suite, h.pk, []byte(formID), signature) + err = signed.GetAndVerify(form.pk, &req) if err != nil { - ForbiddenError(w, r, xerrors.Errorf("signature verification failed: %v", err), nil) + InternalError(w, r, getSignedErr(err), nil) return } deleteForm := types.DeleteForm{ FormID: formID, - UserID: userID, + UserID: req.UserID, } - data, err := deleteForm.Serialize(h.context) + data, err := deleteForm.Serialize(form.context) if err != nil { InternalError(w, r, xerrors.Errorf("failed to marshal DeleteForm: %v", err), nil) return } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdDeleteForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdDeleteForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's information - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /addtoadminlist +func (form *form) AddAdmin(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + addAdmin := types.AddAdmin{ + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := addAdmin.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal AddAdmin: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdAddAdmin, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + // send the transaction's information + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /removetoadminlist +func (form *form) RemoveAdmin(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + removeAdmin := types.RemoveAdmin{ + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := removeAdmin.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal RemoveAdmin: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdRemoveAdmin, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// GET /adminlist +func (form *form) AdminList(w http.ResponseWriter, r *http.Request) { + adminList, err := types.AdminListFromStore(form.context, form.adminFac, form.orderingSvc.GetStore(), evoting.AdminListId) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to get form: %v", err), nil) + return + } + + // Used to check whether it is the last SCIPER printed + count := 0 + lenAdminList := len(adminList.AdminList) + + myAdminList := "{" + for id := range adminList.AdminList { + count++ + + // If last element, does not add ', ' otherwise add ', ' + if count == lenAdminList { + myAdminList += strconv.Itoa(adminList.AdminList[id]) + } else { + myAdminList += strconv.Itoa(adminList.AdminList[id]) + ", " + } + + } + myAdminList += "}" + + txnmanager.SendResponse(w, myAdminList) +} + +// POST /forms/{formID}/addowner +func (form *form) AddOwnerToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + addOwner := types.AddOwner{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := addOwner.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal AddOwner: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdAddOwnerForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /forms/{formID}/removeowner +func (form *form) RemoveOwnerToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + removeOwner := types.RemoveOwner{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := removeOwner.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal RemoveOwner: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdRemoveOwnerForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /forms/{formID}/addvoter +func (form *form) AddVoterToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + addVoter := types.AddVoter{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := addVoter.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal AddVoter: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdAddVoterForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /forms/{formID}/removevoter +func (form *form) RemoveVoterToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + removeVoter := types.RemoveVoter{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := removeVoter.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal RemoveVoter: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdRemoveVoterForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } -func (h *form) getFormsMetadata() (types.FormsMetadata, error) { +// ===== HELPER ===== + +func (form *form) getFormsMetadata() (types.FormsMetadata, error) { var md types.FormsMetadata - store, err := h.orderingSvc.GetStore().Get([]byte(evoting.FormsMetadataKey)) + store, err := form.orderingSvc.GetStore().Get([]byte(evoting.FormsMetadataKey)) if err != nil { return md, nil } @@ -595,3 +828,47 @@ func (h *form) getFormsMetadata() (types.FormsMetadata, error) { return md, nil } + +func (form *form) getPermissionOpRequest(w http.ResponseWriter, r *http.Request) (ptypes.PermissionOperationRequest, error) { + var req ptypes.PermissionOperationRequest + + // get the signed request + signed, err := ptypes.NewSignedRequest(r.Body) + if err != nil { + InternalError(w, r, newSignedErr(err), nil) + return ptypes.PermissionOperationRequest{}, err + } + + // get the request and verify the signature + err = signed.GetAndVerify(form.pk, &req) + if err != nil { + InternalError(w, r, getSignedErr(err), nil) + return ptypes.PermissionOperationRequest{}, err + } + return req, err +} + +func (form *form) extractAndRetrieveFormID(w http.ResponseWriter, r *http.Request) (string, bool) { + vars := mux.Vars(r) + + // check if the formID is valid + if vars == nil || vars["formID"] == "" { + http.Error(w, fmt.Sprintf("formID not found: %v", vars), http.StatusInternalServerError) + return "", true + } + + formID := vars["formID"] + + elecMD, err := form.getFormsMetadata() + if err != nil { + http.Error(w, "failed to get form metadata", http.StatusNotFound) + return "", true + } + + // check if the form exists + if elecMD.FormsIDs.Contains(formID) < 0 { + http.Error(w, "the form does not exist", http.StatusNotFound) + return "", true + } + return formID, false +} diff --git a/proxy/generate_test.go b/proxy/generate_test.go new file mode 100644 index 000000000..8fa88d38e --- /dev/null +++ b/proxy/generate_test.go @@ -0,0 +1,51 @@ +package proxy + +import ( + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3/sign/schnorr" + "testing" +) + +/* +TestGenerateSignatureAndB64Payload is a code snippet that help developer +to generate Signature and Base64 Payload to create cURL request for +debug environment. + +How to use it: +- Feel free to change the INPUT following docs/api.md +- Run the test and use the provided result as a cURL request. +*/ +func TestGenerateSignatureAndB64Payload(t *testing.T) { + // #### INPUT #### + // rawPayload must be built following docs/api.md + rawPayload := `{"TargetUserID" : "654321", "PerformingUserID" : "123456"}` + + // pk must be set according to the public key used to run the system. + pk := "6aadf480d068ac896330b726802abd0da2a5f3824f791fe8dbd4cd555e80b809" + // #### END INPUT #### + + // #### DO NOT MODIFY BELOW #### + payload := base64.URLEncoding.EncodeToString([]byte(rawPayload)) + + hash := sha256.New() + hash.Write([]byte(payload)) + md := hash.Sum(nil) + + pkhex, err := hex.DecodeString(pk) + require.NoError(t, err) + + point := suite.Scalar() + err = point.UnmarshalBinary(pkhex) + require.NoError(t, err) + + require.NoError(t, err) + + signature, err := schnorr.Sign(suite, point, md) + require.NoError(t, err) + + println("Signature: " + hex.EncodeToString(signature)) + println("Payload: " + payload) +} diff --git a/proxy/mod.go b/proxy/mod.go index dbd1cb9cb..b5e2be50f 100644 --- a/proxy/mod.go +++ b/proxy/mod.go @@ -31,6 +31,21 @@ type Form interface { Form(http.ResponseWriter, *http.Request) // DELETE /forms/{formID} DeleteForm(http.ResponseWriter, *http.Request) + // TODO CHECK CAUSE NEW -> modif according to blockchain + // POST /addadmin + AddAdmin(http.ResponseWriter, *http.Request) + // POST /removeadmin + RemoveAdmin(http.ResponseWriter, *http.Request) + // GET /adminlist + AdminList(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/addowner + AddOwnerToForm(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/removeowner + RemoveOwnerToForm(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/addvoter + AddVoterToForm(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/removevoter + RemoveVoterToForm(http.ResponseWriter, *http.Request) } // DKG defines the public HTTP API of the DKG service diff --git a/proxy/txnmanager/transaction.go b/proxy/txnmanager/transaction.go index 0cce21e7a..f8f189629 100644 --- a/proxy/txnmanager/transaction.go +++ b/proxy/txnmanager/transaction.go @@ -7,6 +7,7 @@ import ( b64 "encoding/base64" "encoding/json" "fmt" + "go.dedis.ch/dela/core/validation" "net/http" "strconv" "sync" @@ -32,7 +33,7 @@ const ( // NewTransactionManager returns a new initialized transaction manager func NewTransactionManager(mngr txn.Manager, p pool.Pool, - ctx serde.Context, pk kyber.Point, blocks blockstore.BlockStore, signer crypto.Signer) Manager { + ctx serde.Context, pk kyber.Point, blocks blockstore.BlockStore, signer crypto.Signer, val validation.Service) Manager { logger := dela.Logger.With().Timestamp().Str("role", "proxy-txmanager").Logger() @@ -44,6 +45,7 @@ func NewTransactionManager(mngr txn.Manager, p pool.Pool, pk: pk, blocks: blocks, signer: signer, + val: val, } } @@ -60,6 +62,7 @@ type manager struct { pk kyber.Point blocks blockstore.BlockStore signer crypto.Signer + val validation.Service } // StatusHandlerGet checks if the transaction is included in the blockchain @@ -183,10 +186,19 @@ func (h *manager) checkTxnIncluded(transactionID []byte, lastBlockIdx uint64) (T } // check if the transaction is in the block - transactions := blockLink.GetBlock().GetTransactions() + transactions := blockLink.GetBlock().GetData().GetTransactionResults() //blockLink.GetBlock().GetTransactions() + for _, txn := range transactions { - if bytes.Equal(txn.GetID(), transactionID) { - return IncludedTransaction, blockLink.GetBlock().GetIndex() + + if bytes.Equal(txn.GetTransaction().GetID(), transactionID) { + accepted, reason := txn.GetStatus() + if accepted { + return IncludedTransaction, blockLink.GetBlock().GetIndex() + } else { + println(reason) + return RejectedTransaction, blockLink.GetBlock().GetIndex() + } + } } diff --git a/proxy/types/election.go b/proxy/types/election.go index fd8775d7b..8058b50b4 100644 --- a/proxy/types/election.go +++ b/proxy/types/election.go @@ -10,6 +10,13 @@ type CreateFormRequest struct { Configuration etypes.Configuration } +// PermissionOperationRequest defines the HTTP request for performing +// an operation request +type PermissionOperationRequest struct { + TargetUserID string + PerformingUserID string +} + // CreateFormResponse defines the HTTP response when creating a form type CreateFormResponse struct { FormID string // hex-encoded @@ -35,6 +42,7 @@ type EGPairJSON struct { // UpdateFormRequest defines the HTTP request for updating a form type UpdateFormRequest struct { Action string + UserID string } // GetFormResponse defines the HTTP response when getting the form info