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 }