Commit c17cfe72 authored by Tristan Claverie's avatar Tristan Claverie

Merge branch '293_mails_fixes' into 'master'

[c][p] Some fixes after real tests

- Add a method in `dfss/auth` package to compute a certificate's hash
- Update `dfssp/contract.Builder` to allow `dfssp/user` package to
  trigger some mail emissions
- Update `dfssp/user` to fix authentication (bad expiration and bad
  certHash)
- Fix a critical bug in client during creation of new contract (nil
  pointer)

TG-293 #ready

See merge request !29
parents 569e5393 c8682453
Pipeline #295 passed with stages
......@@ -3,6 +3,7 @@ package auth
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
......@@ -116,6 +117,12 @@ func PEMToCertificate(data []byte) (*x509.Certificate, error) {
return x509.ParseCertificate(block.Bytes)
}
// GetCertificateHash returns the SHA512 hash of a certificate
func GetCertificateHash(cert *x509.Certificate) []byte {
h := sha512.Sum512(cert.Raw)
return h[:]
}
// GenerateUID generates a unique identifier as a uint64
func GenerateUID() uint64 {
// Generating and converting the uuid to fit our needs: an 8 bytes unsigned integer
......
......@@ -145,6 +145,16 @@ func TestGetCertificate(t *testing.T) {
}
func TestGetCertificateHash(t *testing.T) {
crt, _ := PEMToCertificate([]byte(crtFixture))
res := GetCertificateHash(crt)
expected := "5c68072d750aa6c24c96e0b984ed399b726f8664456ed74259841b207b43159c5eaddf436d8f28c48b3ef6c83e69d68d115f0f0e4cf71001c2ca599a6ff7a0c1"
if fmt.Sprintf("%x", res) != expected {
t.Fatalf("Bad hash")
}
}
func ExampleGetCertificate() {
// Load elements from PEM files
......
......@@ -19,6 +19,8 @@ func newContract() {
// getContractInfo asks user for contract informations
func getContractInfo() (passphrase string, path string, comment string, signers []string) {
signers = make([]string, 1)
var signersBuf string
_ = readPassword(&passphrase, false)
readStringParam("Contract path", "", &path)
......
......@@ -34,7 +34,7 @@ func TestMain(m *testing.M) {
os.Exit(1)
}
collection = manager.Get("demo")
collection = manager.Get("contracts")
repository = entities.NewContractRepository(collection)
// Start platform server
......@@ -83,6 +83,7 @@ func assertContractEqual(t *testing.T, contract, fetched entities.Contract) {
// Insert a contract with 2 users and check the fields are correctly persisted
func TestInsertContract(t *testing.T) {
dropDataset()
c := entities.NewContract()
c.AddSigner(nil, "mail1", "hash1")
c.AddSigner(nil, "mail1", "hash1")
......@@ -109,3 +110,32 @@ func TestInsertContract(t *testing.T) {
assertContractEqual(t, *c, fetched)
}
// Insert some contracts with missing user and test waiting contracts for this user
func TestGetWaitingForUser(t *testing.T) {
knownID := bson.NewObjectId()
dropDataset()
c1 := entities.NewContract()
c1.AddSigner(nil, "mail1", "")
c1.Ready = false
c2 := entities.NewContract()
c2.AddSigner(nil, "mail1", "")
c2.AddSigner(&knownID, "mail2", "hash")
c2.Ready = false
c3 := entities.NewContract()
c3.AddSigner(nil, "mail2", "")
c3.AddSigner(&knownID, "mail1", "hash")
c3.Ready = false
_, _ = repository.Collection.Insert(c1)
_, _ = repository.Collection.Insert(c2)
_, _ = repository.Collection.Insert(c3)
contracts, err := repository.GetWaitingForUser("mail1")
assert.Equal(t, nil, err)
assert.Equal(t, 2, len(contracts))
}
......@@ -18,7 +18,7 @@ type Builder struct {
in *api.PostContractRequest
signers []entities.User
missingSigners []string
contract *entities.Contract
Contract *entities.Contract
}
// NewContractBuilder creates a new builder from current context.
......@@ -54,7 +54,7 @@ func (c *Builder) Execute() *api.ErrorCode {
c.sendPendingContractMail()
return &api.ErrorCode{Code: api.ErrorCode_WARNING, Message: "Some users are not ready yet"}
}
c.sendNewContractMail()
c.SendNewContractMail()
return &api.ErrorCode{Code: api.ErrorCode_SUCCESS}
}
......@@ -127,41 +127,41 @@ func (c *Builder) addContract() error {
contract.File.Hosted = false
_, err := c.m.Get("contracts").Insert(contract)
c.contract = contract
c.Contract = contract
return err
}
// sendNewContractMail sends a mail to each known signer in a contract containing the DFSS file
func (c *Builder) sendNewContractMail() {
// SendNewContractMail sends a mail to each known signer in a contract containing the DFSS file
func (c *Builder) SendNewContractMail() {
conn := templates.MailConn()
if conn == nil {
return
}
defer func() { _ = conn.Close() }()
rcpts := make([]string, len(c.contract.Signers))
for i, s := range c.contract.Signers {
rcpts := make([]string, len(c.Contract.Signers))
for i, s := range c.Contract.Signers {
rcpts[i] = s.Email
}
content, err := templates.Get("contract", c)
content, err := templates.Get("contract", c.Contract)
if err != nil {
log.Println(err)
return
}
file, err := GetJSON(c.contract, nil)
file, err := GetJSON(c.Contract, nil)
if err != nil {
log.Println(err)
return
}
fileSmallHash := c.contract.ID.Hex()[0:8] // Pretty hash instead of huge one
fileSmallHash := c.Contract.ID.Hex()
_ = conn.Send(
rcpts,
"[DFSS] You are invited to sign "+c.contract.File.Name,
"[DFSS] You are invited to sign "+c.Contract.File.Name,
content,
[]string{"application/json"},
[]string{fileSmallHash + ".json"},
......@@ -177,11 +177,11 @@ func (c *Builder) sendPendingContractMail() {
}
defer func() { _ = conn.Close() }()
content, err := templates.Get("invitation", c)
content, err := templates.Get("invitation", c.Contract)
if err != nil {
log.Println(err)
return
}
_ = conn.Send(c.missingSigners, "[DFSS] You are invited to sign "+c.contract.File.Name, content, nil, nil, nil)
_ = conn.Send(c.missingSigners, "[DFSS] You are invited to sign "+c.Contract.File.Name, content, nil, nil, nil)
}
......@@ -69,3 +69,17 @@ func NewContractRepository(collection *mgdb.MongoCollection) *ContractRepository
collection,
}
}
// GetWaitingForUser returns contracts waiting a specific unauthenticated user to start
func (r *ContractRepository) GetWaitingForUser(email string) ([]Contract, error) {
var res []Contract
err := r.Collection.FindAll(bson.M{
"ready": false,
"signers": bson.M{
"$elemMatch": bson.M{
"email" : email,
"hash" : "",
}},
}, &res)
return res, err
}
......@@ -3,16 +3,16 @@ package user
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"errors"
"fmt"
"io"
"log"
"time"
"dfss/auth"
"dfss/dfssp/api"
"dfss/dfssp/authority"
"dfss/dfssp/contract"
"dfss/dfssp/entities"
"dfss/dfssp/templates"
"dfss/mgdb"
......@@ -167,12 +167,8 @@ func generateUserCert(csr string, certDuration int, parent *x509.Certificate, ke
return nil, nil, err
}
h := sha512.New()
_, err = io.WriteString(h, string(cert))
if err != nil {
return nil, nil, err
}
certHash := h.Sum(nil)
c, _ := auth.PEMToCertificate(cert)
certHash := auth.GetCertificateHash(c)
return cert, certHash, nil
}
......@@ -227,7 +223,8 @@ func Auth(pid *authority.PlatformID, manager *mgdb.MongoManager, certDuration in
}
user.Certificate = string(cert)
user.CertHash = string(certHash)
user.CertHash = fmt.Sprintf("%x", certHash)
user.Expiration = time.Now().AddDate(0, 0, certDuration)
// Updating the database
ok, err := manager.Get("users").UpdateByID(user)
......@@ -235,6 +232,46 @@ func Auth(pid *authority.PlatformID, manager *mgdb.MongoManager, certDuration in
return nil, err
}
// Update missed contracts in background
go launchMissedContracts(manager, &user)
// Returning the RegisteredUser message
return &api.RegisteredUser{ClientCert: user.Certificate}, nil
}
func launchMissedContracts(manager *mgdb.MongoManager, user *entities.User) {
repository := entities.NewContractRepository(manager.Get("contracts"))
contracts, err := repository.GetWaitingForUser(user.Email)
if err != nil {
log.Println("Cannot get missed contracts for user", user.Email + ":", err)
}
for _, c := range contracts {
c.Ready = true
for i := range c.Signers {
if c.Signers[i].Email == user.Email {
c.Signers[i].Hash = user.CertHash
c.Signers[i].UserID = user.ID
}
if len(c.Signers[i].Hash) == 0 {
c.Ready = false
}
}
// Update contract in database
_, err = repository.Collection.UpdateByID(c)
if err != nil {
log.Println("Cannot update missed contract", c.ID, "for user", user.Email + ":", err)
}
if c.Ready {
// Send required mails
builder := contract.NewContractBuilder(manager, nil)
builder.Contract = &c
builder.SendNewContractMail()
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment