github.com/decred/politeia@v1.4.0/politeiawww/cmd/cmswww/newdcc.go (about)

     1  // Copyright (c) 2017-2019 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  
     5  package main
     6  
     7  import (
     8  	"bufio"
     9  	"encoding/base64"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"fmt"
    13  	"os"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/decred/politeia/politeiad/api/v1/mime"
    18  	cms "github.com/decred/politeia/politeiawww/api/cms/v1"
    19  	www "github.com/decred/politeia/politeiawww/api/www/v1"
    20  	"github.com/decred/politeia/politeiawww/cmd/shared"
    21  	"github.com/decred/politeia/util"
    22  )
    23  
    24  // domainTypes gives human readable output for the various domain types available
    25  var domainTypes = map[cms.DomainTypeT]string{
    26  	cms.DomainTypeDeveloper: "(1) Developer",
    27  	cms.DomainTypeMarketing: "(2) Marketing",
    28  	cms.DomainTypeResearch:  "(4) Research",
    29  	cms.DomainTypeDesign:    "(5) Design",
    30  }
    31  
    32  // contractorTypes gives human readable output for the various contractor types available
    33  var contractorTypes = map[cms.ContractorTypeT]string{
    34  	cms.ContractorTypeDirect:        "(1) Direct",
    35  	cms.ContractorTypeSubContractor: "(3) Sub-contractor",
    36  }
    37  
    38  // NewDCCCmd submits a new dcc.
    39  type NewDCCCmd struct {
    40  	Args struct {
    41  		Type        uint     `positional-arg-name:"type"`            // 1 for Issuance, 2 for Revocation
    42  		Attachments []string `positional-arg-name:"attachmentfiles"` // DCC attachment files
    43  	} `positional-args:"true" optional:"true"`
    44  	NomineeUserID  string `long:"nomineeuserid" optional:"true" description:"The UserID of the Nominated User"`
    45  	Statement      string `long:"statement" optional:"true" description:"Statement in support of the DCC"`
    46  	Domain         string `long:"domain" optional:"true" description:"The domain of the nominated user"`
    47  	ContractorType string `long:"contractortype" optional:"true" description:"The contractor type of the nominated user"`
    48  }
    49  
    50  // Execute executes the new dcc command.
    51  func (cmd *NewDCCCmd) Execute(args []string) error {
    52  	// Check for a valid DCC type
    53  	if int(cmd.Args.Type) <= 0 || int(cmd.Args.Type) > 2 {
    54  		return errInvalidDCCType
    55  	}
    56  
    57  	// Check for user identity
    58  	if cfg.Identity == nil {
    59  		return shared.ErrUserIdentityNotFound
    60  	}
    61  
    62  	// Get server public key
    63  	vr, err := client.Version()
    64  	if err != nil {
    65  		return err
    66  	}
    67  	var domainType int
    68  	var contractorType int
    69  	if cmd.Statement == "" || cmd.NomineeUserID == "" || cmd.Domain == "" ||
    70  		cmd.ContractorType == "" {
    71  		reader := bufio.NewReader(os.Stdin)
    72  		if cmd.Statement == "" {
    73  			fmt.Print("Enter your statement to support the DCC: ")
    74  			cmd.Statement, _ = reader.ReadString('\n')
    75  		}
    76  		if cmd.NomineeUserID == "" {
    77  			fmt.Print("Enter the nominee user id: ")
    78  			cmd.NomineeUserID, _ = reader.ReadString('\n')
    79  		}
    80  		uir, err := client.CMSUserDetails(strings.TrimSpace(cmd.NomineeUserID))
    81  		if err != nil {
    82  			return err
    83  		}
    84  		if uir.User.ID == "" {
    85  			return fmt.Errorf("nominee user not found, please try again")
    86  		}
    87  		if cmd.Domain == "" &&
    88  			cmd.Args.Type == uint(cms.DCCTypeIssuance) {
    89  			for {
    90  				fmt.Printf("Domain Type: " +
    91  					domainTypes[cms.DomainTypeDeveloper] + ", " +
    92  					domainTypes[cms.DomainTypeMarketing] + ", " +
    93  					domainTypes[cms.DomainTypeResearch] + ", " +
    94  					domainTypes[cms.DomainTypeDesign] + ", " + ": ")
    95  				cmd.Domain, _ = reader.ReadString('\n')
    96  				domainType, err = strconv.Atoi(strings.TrimSpace(cmd.Domain))
    97  				if err != nil {
    98  					fmt.Println("Invalid entry, please try again.")
    99  					continue
   100  				}
   101  				if domainType < 1 || domainType > 6 {
   102  					fmt.Println("Invalid domain type entered, please try again.")
   103  					continue
   104  				}
   105  				str := fmt.Sprintf(
   106  					"Your current Domain setting is: \"%v\" Keep this?",
   107  					domainType)
   108  				update, err := promptListBool(reader, str, "yes")
   109  				if err != nil {
   110  					return err
   111  				}
   112  				if update {
   113  					break
   114  				}
   115  			}
   116  		} else if cmd.Domain == "" &&
   117  			cmd.Args.Type == uint(cms.DCCTypeRevocation) {
   118  			// Set domain type to what the user is currently set to
   119  			domainType = int(uir.User.Domain)
   120  		} else {
   121  			domainType, err = strconv.Atoi(strings.TrimSpace(cmd.Domain))
   122  			if err != nil {
   123  				return fmt.Errorf("invalid domain type: %v", err)
   124  			}
   125  			if domainType < 1 || domainType > 6 {
   126  				return fmt.Errorf("invalid domain type")
   127  			}
   128  		}
   129  		if cmd.ContractorType == "" &&
   130  			cmd.Args.Type == uint(cms.DCCTypeIssuance) {
   131  			for {
   132  				fmt.Printf("Contractor Type: " +
   133  					contractorTypes[cms.ContractorTypeDirect] + ", " +
   134  					contractorTypes[cms.ContractorTypeSubContractor] + ": ")
   135  				cmd.ContractorType, _ = reader.ReadString('\n')
   136  				contractorType, err = strconv.Atoi(strings.TrimSpace(cmd.ContractorType))
   137  				if err != nil {
   138  					fmt.Println("Invalid entry, please try again.")
   139  					continue
   140  				}
   141  				if contractorType != int(cms.ContractorTypeDirect) &&
   142  					contractorType != int(cms.ContractorTypeSubContractor) {
   143  					fmt.Println("Invalid contractor type entered, please try again.")
   144  					continue
   145  				}
   146  				str := fmt.Sprintf(
   147  					"Your current Contractor Type setting is: \"%v\" Keep this?",
   148  					domainType)
   149  				update, err := promptListBool(reader, str, "yes")
   150  				if err != nil {
   151  					return err
   152  				}
   153  				if update {
   154  					break
   155  				}
   156  			}
   157  		} else if cmd.ContractorType == "" &&
   158  			cmd.Args.Type == uint(cms.DCCTypeRevocation) {
   159  			// Set the contractor type to Revoked for revocation DCCs
   160  			contractorType = int(cms.ContractorTypeRevoked)
   161  		} else {
   162  			contractorType, err = strconv.Atoi(strings.TrimSpace(cmd.ContractorType))
   163  			if err != nil {
   164  				return fmt.Errorf("invalid contractor type: %v", err)
   165  			}
   166  			if contractorType != 1 && contractorType != 3 {
   167  				return fmt.Errorf("invalid contractor type")
   168  			}
   169  		}
   170  		fmt.Print("\nPlease carefully review your information and ensure it's " +
   171  			"correct. If not, press Ctrl + C to exit. Or, press Enter to continue.")
   172  		reader.ReadString('\n')
   173  	}
   174  
   175  	// XXX the above logic is missing validation for when the flags are
   176  	// set. Do it here for now.
   177  	if cmd.Domain != "" {
   178  		i, err := strconv.Atoi(cmd.Domain)
   179  		if err != nil {
   180  			return fmt.Errorf("parse domain: %v", err)
   181  		}
   182  		domainType = i
   183  	}
   184  	if cmd.ContractorType != "" {
   185  		i, err := strconv.Atoi(cmd.ContractorType)
   186  		if err != nil {
   187  			return fmt.Errorf("parse contractor type: %v", err)
   188  		}
   189  		contractorType = i
   190  	}
   191  
   192  	dccInput := &cms.DCCInput{}
   193  	dccInput.SponsorStatement = strings.TrimSpace(cmd.Statement)
   194  	dccInput.NomineeUserID = strings.TrimSpace(cmd.NomineeUserID)
   195  	dccInput.Type = cms.DCCTypeT(int(cmd.Args.Type))
   196  	dccInput.Domain = cms.DomainTypeT(domainType)
   197  	dccInput.ContractorType = cms.ContractorTypeT(contractorType)
   198  
   199  	// Print request details
   200  	err = shared.PrintJSON(dccInput)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	b, err := json.Marshal(dccInput)
   205  	if err != nil {
   206  		return fmt.Errorf("Marshal: %v", err)
   207  	}
   208  
   209  	f := www.File{
   210  		Name:    "dcc.json",
   211  		MIME:    mime.DetectMimeType(b),
   212  		Digest:  hex.EncodeToString(util.Digest(b)),
   213  		Payload: base64.StdEncoding.EncodeToString(b),
   214  	}
   215  
   216  	files := make([]www.File, 0, 1)
   217  	files = append(files, f)
   218  
   219  	// Compute merkle root and sign it
   220  	sig, err := signedMerkleRoot(files, nil, cfg.Identity)
   221  	if err != nil {
   222  		return fmt.Errorf("SignMerkleRoot: %v", err)
   223  	}
   224  
   225  	// Setup new dcc request
   226  	nd := cms.NewDCC{
   227  		File:      f,
   228  		PublicKey: hex.EncodeToString(cfg.Identity.Public.Key[:]),
   229  		Signature: sig,
   230  	}
   231  
   232  	// Print request details
   233  	err = shared.PrintJSON(nd)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	// Send request
   239  	ndr, err := client.NewDCC(nd)
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	// Verify the censorship record
   245  	dcc := cms.DCCRecord{
   246  		File:             nd.File,
   247  		PublicKey:        nd.PublicKey,
   248  		Signature:        nd.Signature,
   249  		CensorshipRecord: ndr.CensorshipRecord,
   250  	}
   251  	err = verifyDCC(dcc, vr.PubKey)
   252  	if err != nil {
   253  		return fmt.Errorf("unable to verify proposal %v: %v",
   254  			dcc.CensorshipRecord.Token, err)
   255  	}
   256  
   257  	// Print response details
   258  	return shared.PrintJSON(ndr)
   259  }