create.go 4.25 KB
Newer Older
1 2 3 4 5 6 7 8 9
package contract

import (
	"crypto/sha512"
	"log"
	"time"

	"dfss/dfssp/api"
	"dfss/dfssp/entities"
10
	"dfss/dfssp/templates"
11 12 13 14
	"dfss/mgdb"
	"gopkg.in/mgo.v2/bson"
)

15 16 17 18 19 20
// Builder contains internal information to create a new contract.
type Builder struct {
	m              *mgdb.MongoManager
	in             *api.PostContractRequest
	signers        []entities.User
	missingSigners []string
21
	Contract       *entities.Contract
22 23 24 25 26 27 28 29 30 31 32 33 34
}

// NewContractBuilder creates a new builder from current context.
// Call Execute() on the builder to get a result from it.
func NewContractBuilder(m *mgdb.MongoManager, in *api.PostContractRequest) *Builder {
	return &Builder{
		m:  m,
		in: in,
	}
}

// Execute triggers the creation of a new contract.
func (c *Builder) Execute() *api.ErrorCode {
35

36
	inputError := c.checkInput()
37 38 39 40
	if inputError != nil {
		return inputError
	}

41
	err := c.fetchSigners()
42 43 44 45 46
	if err != nil {
		log.Println(err)
		return &api.ErrorCode{Code: api.ErrorCode_INTERR, Message: "Database error"}
	}

47
	err = c.addContract()
48 49 50 51 52
	if err != nil {
		log.Println(err)
		return &api.ErrorCode{Code: api.ErrorCode_INTERR}
	}

53 54
	if len(c.missingSigners) > 0 {
		c.sendPendingContractMail()
55 56
		return &api.ErrorCode{Code: api.ErrorCode_WARNING, Message: "Some users are not ready yet"}
	}
57
	c.SendNewContractMail()
58 59 60 61
	return &api.ErrorCode{Code: api.ErrorCode_SUCCESS}

}

62 63
// checkInput checks that a PostContractRequest is well-formed
func (c *Builder) checkInput() *api.ErrorCode {
64

65
	if len(c.in.Signer) == 0 {
66 67 68
		return &api.ErrorCode{Code: api.ErrorCode_INVARG, Message: "Expecting at least one signer"}
	}

69
	if len(c.in.Filename) == 0 {
70 71 72
		return &api.ErrorCode{Code: api.ErrorCode_INVARG, Message: "Expecting a valid filename"}
	}

73
	if len(c.in.Hash) != sha512.Size {
74 75 76 77 78 79 80
		return &api.ErrorCode{Code: api.ErrorCode_INVARG, Message: "Expecting a valid sha512 hash"}
	}

	return nil

}

81 82
// fetchSigners fetches authenticated users for this contract from the DB
func (c *Builder) fetchSigners() error {
83
	var users []entities.User
84 85 86 87

	// Fetch users where email is part of the signers slice in request
	// and authentication is valid
	err := c.m.Get("users").FindAll(bson.M{
88
		"expiration": bson.M{"$gt": time.Now()},
89
		"email":      bson.M{"$in": c.in.Signer},
90 91
	}, &users)
	if err != nil {
92
		return err
93 94 95
	}

	// Locate missing users
96
	for _, s := range c.in.Signer {
97 98 99 100 101 102 103 104
		found := false
		for _, u := range users {
			if s == u.Email {
				found = true
				break
			}
		}
		if !found {
105
			c.missingSigners = append(c.missingSigners, s) // a list of not valid mail adress
106 107 108
		}
	}

109 110
	c.signers = users
	return nil
111 112
}

113 114
// addContract inserts the contract into the DB
func (c *Builder) addContract() error {
115
	contract := entities.NewContract()
116
	for _, s := range c.signers {
117 118
		contract.AddSigner(&s.ID, s.Email, s.CertHash)
	}
119
	for _, s := range c.missingSigners {
120
		contract.AddSigner(nil, s, nil)
121 122
	}

123 124 125 126
	contract.Comment = c.in.Comment
	contract.Ready = len(c.missingSigners) == 0
	contract.File.Name = c.in.Filename
	contract.File.Hash = c.in.Hash
127 128
	contract.File.Hosted = false

129
	_, err := c.m.Get("contracts").Insert(contract)
130
	c.Contract = contract
131 132

	return err
133 134
}

135 136
// SendNewContractMail sends a mail to each known signer in a contract containing the DFSS file
func (c *Builder) SendNewContractMail() {
137 138 139 140 141 142
	conn := templates.MailConn()
	if conn == nil {
		return
	}
	defer func() { _ = conn.Close() }()

143 144
	rcpts := make([]string, len(c.Contract.Signers))
	for i, s := range c.Contract.Signers {
145 146 147
		rcpts[i] = s.Email
	}

148
	content, err := templates.Get("contract", c.Contract)
149 150 151 152 153
	if err != nil {
		log.Println(err)
		return
	}

154
	file, err := GetJSON(c.Contract, nil)
155 156 157 158 159
	if err != nil {
		log.Println(err)
		return
	}

160
	fileSmallHash := c.Contract.ID.Hex()
161

162 163
	_ = conn.Send(
		rcpts,
164
		"[DFSS] You are invited to sign "+c.Contract.File.Name,
165 166
		content,
		[]string{"application/json"},
167
		[]string{fileSmallHash + ".json"},
168 169 170 171
		[][]byte{file},
	)
}

172 173
// sendPendingContractMail sends a mail to non-authenticated signers to invite them
func (c *Builder) sendPendingContractMail() {
174 175 176 177 178 179
	conn := templates.MailConn()
	if conn == nil {
		return
	}
	defer func() { _ = conn.Close() }()

180
	content, err := templates.Get("invitation", c.Contract)
181 182 183 184 185
	if err != nil {
		log.Println(err)
		return
	}

186
	_ = conn.Send(c.missingSigners, "[DFSS] You are invited to sign "+c.Contract.File.Name, content, nil, nil, nil)
187
}