diff --git a/core/core.go b/core/core.go
index 0362703..cad4034 100644
--- a/core/core.go
+++ b/core/core.go
@@ -9,16 +9,22 @@ import (
"errors"
"fmt"
"log"
+ "strconv"
+ "time"
"github.com/cloudflare/redoctober/cryptor"
+ "github.com/cloudflare/redoctober/hipchat"
"github.com/cloudflare/redoctober/keycache"
+ "github.com/cloudflare/redoctober/order"
"github.com/cloudflare/redoctober/passvault"
)
var (
- crypt cryptor.Cryptor
- records passvault.Records
- cache keycache.Cache
+ crypt cryptor.Cryptor
+ records passvault.Records
+ cache keycache.Cache
+ orders order.Orderer
+ alternateName string
)
// Each of these structures corresponds to the JSON expected on the
@@ -63,6 +69,7 @@ type PasswordRequest struct {
Password string
NewPassword string
+ HipchatName string
}
type EncryptRequest struct {
@@ -105,6 +112,26 @@ type ExportRequest struct {
Password string
}
+type OrderRequest struct {
+ Name string
+ Password string
+ Duration string
+ Uses string
+ Data []byte
+ Label string
+}
+
+type OrderInfoRequest struct {
+ Name string
+ Password string
+
+ OrderNum string
+}
+type OrderOutstandingRequest struct {
+ Name string
+ Password string
+}
+
// These structures map the JSON responses that will be sent from the API
type ResponseData struct {
@@ -145,6 +172,26 @@ func jsonResponse(resp []byte) ([]byte, error) {
return json.Marshal(ResponseData{Status: "ok", Response: resp})
}
+func getAltNameFromName(alt, name string) (altName string, found bool) {
+ if passwordRecord, ok := records.Passwords[name]; ok {
+ if altName, ok := passwordRecord.AltNames[alt]; ok {
+ return altName, true
+ }
+ }
+ return "", false
+}
+func getAltNamesFromNames(alt string, names []string) map[string]string {
+ altNames := make(map[string]string)
+ for _, name := range names {
+ altName, found := getAltNameFromName(alt, name)
+ if !found {
+ altName = name
+ }
+ altNames[name] = altName
+ }
+ return altNames
+}
+
// validateUser checks that the username and password passed in are
// correct. If admin is true, the user must be an admin as well.
func validateUser(name, password string, admin bool) error {
@@ -168,8 +215,7 @@ func validateUser(name, password string, admin bool) error {
return nil
}
-// validateName checks that the username and password pass the minimal
-// validation check
+// validateName checks that the username and password pass a validation test.
func validateName(name, password string) error {
if name == "" {
return errors.New("User name must not be blank")
@@ -182,23 +228,37 @@ func validateName(name, password string) error {
}
// Init reads the records from disk from a given path
-func Init(path string) error {
+func Init(ca, hcKey, hcRoom, hcHost, roHost string) error {
var err error
defer func() {
if err != nil {
log.Printf("core.init failed: %v", err)
} else {
- log.Printf("core.init success: path=%s", path)
+ log.Printf("core.init success: ca=%s", ca)
}
}()
- if records, err = passvault.InitFrom(path); err != nil {
- err = fmt.Errorf("failed to load password vault %s: %s", path, err)
+ if records, err = passvault.InitFrom(ca); err != nil {
+ err = fmt.Errorf("failed to load password vault %s: %s", ca, err)
}
+ if hcKey != "" && hcRoom != "" && hcHost != "" {
+ alternateName = "HipchatName"
+ roomId, err := strconv.Atoi(hcRoom)
+ if err != nil {
+ return errors.New("core.init unable to use hipchat roomId provided")
+ }
+ orders.Hipchat = hipchat.HipchatClient{
+ ApiKey: hcKey,
+ RoomId: roomId,
+ HcHost: hcHost,
+ RoHost: roHost,
+ }
+ }
cache = keycache.Cache{UserKeys: make(map[keycache.DelegateIndex]keycache.ActiveUser)}
- crypt = cryptor.New(&records, &cache)
+ orders.PrepareOrders()
+ crypt = cryptor.New(&records, &cache, &orders)
return err
}
@@ -351,6 +411,22 @@ func Delegate(jsonIn []byte) ([]byte, error) {
return jsonStatusError(err)
}
+ // Make sure we capture the number who have already delegated.
+ for _, delegatedUser := range s.Users {
+ for _, delegatedLabel := range s.Labels {
+ if orderKey, found := orders.FindOrder(delegatedUser, delegatedLabel); found {
+ order := orders.Orders[orderKey]
+ order.AdminsDelegated = append(order.AdminsDelegated, s.Name)
+ order.Delegated++
+ orders.Orders[orderKey] = order
+
+ // Notify the hipchat room that there was a new delegator
+ orders.NotifyDelegation(s.Name, delegatedLabel, delegatedUser, orderKey, s.Time)
+
+ }
+ }
+ }
+
return jsonStatusOk()
}
@@ -422,7 +498,7 @@ func Password(jsonIn []byte) ([]byte, error) {
}
// add signed-in record to active set
- err = records.ChangePassword(s.Name, s.Password, s.NewPassword)
+ err = records.ChangePassword(s.Name, s.Password, s.NewPassword, s.HipchatName)
if err != nil {
return jsonStatusError(err)
}
@@ -488,7 +564,7 @@ func ReEncrypt(jsonIn []byte) ([]byte, error) {
return jsonStatusError(err)
}
- data, _, secure, err := crypt.Decrypt(s.Data, s.Name)
+ data, _, _, secure, err := crypt.Decrypt(s.Data, s.Name)
if err != nil {
return jsonStatusError(err)
}
@@ -532,7 +608,7 @@ func Decrypt(jsonIn []byte) ([]byte, error) {
return jsonStatusError(err)
}
- data, names, secure, err := crypt.Decrypt(s.Data, s.Name)
+ data, allLabels, names, secure, err := crypt.Decrypt(s.Data, s.Name)
if err != nil {
return jsonStatusError(err)
}
@@ -548,6 +624,13 @@ func Decrypt(jsonIn []byte) ([]byte, error) {
return jsonStatusError(err)
}
+ // Cleanup any orders that have been fulfilled and notify the room.
+ for _, label := range allLabels {
+ if orderKey, found := orders.FindOrder(s.Name, label); found {
+ delete(orders.Orders, orderKey)
+ orders.NotifyOrderFulfilled(s.Name, orderKey)
+ }
+ }
return jsonResponse(out)
}
@@ -658,3 +741,128 @@ func Export(jsonIn []byte) ([]byte, error) {
return jsonResponse(out)
}
+
+// Order will request delegations from other users.
+func Order(jsonIn []byte) (out []byte, err error) {
+ var o OrderRequest
+
+ defer func() {
+ if err != nil {
+ log.Printf("core.order failed: user=%s %v", o.Name, err)
+ } else {
+ log.Printf("core.order success: user=%s", o.Name)
+ }
+ }()
+
+ if err = json.Unmarshal(jsonIn, &o); err != nil {
+ return jsonStatusError(err)
+ }
+
+ if err := validateUser(o.Name, o.Password, false); err != nil {
+ return jsonStatusError(err)
+ }
+
+ // Get the owners of the ciphertext.
+ owners, _, err := crypt.GetOwners(o.Data)
+ if err != nil {
+ jsonStatusError(err)
+ }
+ if o.Duration == "" {
+ err = errors.New("Duration required when placing an order.")
+ jsonStatusError(err)
+ }
+ if o.Uses == "" || o.Uses == "0" {
+ err = errors.New("Number of required uses necessary when placing an order.")
+ jsonStatusError(err)
+ }
+ cache.Refresh()
+ orderNum := order.GenerateNum()
+
+ adminsDelegated, numDelegated := cache.DelegateStatus(o.Name, o.Label, owners)
+ duration, err := time.ParseDuration(o.Duration)
+ if err != nil {
+ jsonStatusError(err)
+ }
+ currentTime := time.Now()
+ expiryTime := currentTime.Add(duration)
+ ord := order.CreateOrder(o.Name,
+ o.Label,
+ orderNum,
+ currentTime,
+ expiryTime,
+ duration,
+ adminsDelegated,
+ owners,
+ numDelegated)
+ orders.Orders[orderNum] = ord
+ out, err = json.Marshal(ord)
+
+ // Get a map to any alternative name we want to notify
+ altOwners := getAltNamesFromNames(alternateName, owners)
+
+ // Let everyone on hipchat know there is a new order.
+ orders.NotifyNewOrder(o.Name, o.Duration, o.Label, o.Uses, orderNum, altOwners)
+ if err != nil {
+ return jsonStatusError(err)
+ }
+ return jsonResponse(out)
+}
+
+// OrdersOutstanding will return a list of currently outstanding orders.
+func OrdersOutstanding(jsonIn []byte) (out []byte, err error) {
+ var o OrderOutstandingRequest
+
+ defer func() {
+ if err != nil {
+ log.Printf("core.ordersout failed: user=%s %v", o.Name, err)
+ } else {
+ log.Printf("core.ordersout success: user=%s", o.Name)
+ }
+ }()
+
+ if err = json.Unmarshal(jsonIn, &o); err != nil {
+ return jsonStatusError(err)
+ }
+
+ if err := validateUser(o.Name, o.Password, false); err != nil {
+ return jsonStatusError(err)
+ }
+
+ out, err = json.Marshal(orders.Orders)
+ if err != nil {
+ return jsonStatusError(err)
+ }
+ return jsonResponse(out)
+}
+
+// OrderInfo will return a list of currently outstanding order numbers.
+func OrderInfo(jsonIn []byte) (out []byte, err error) {
+ var o OrderInfoRequest
+
+ defer func() {
+ if err != nil {
+ log.Printf("core.order failed: user=%s %v", o.Name, err)
+ } else {
+ log.Printf("core.order success: user=%s", o.Name)
+ }
+ }()
+
+ if err = json.Unmarshal(jsonIn, &o); err != nil {
+ return jsonStatusError(err)
+ }
+
+ if err := validateUser(o.Name, o.Password, false); err != nil {
+ return jsonStatusError(err)
+ }
+
+ if ord, ok := orders.Orders[o.OrderNum]; ok {
+ if out, err = json.Marshal(ord); err != nil {
+ return jsonStatusError(err)
+ } else if len(out) == 0 {
+ return jsonStatusError(errors.New("No order with that number"))
+ }
+
+ return jsonResponse(out)
+ }
+ return
+}
diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go
index fbdf556..26ed154 100644
--- a/cryptor/cryptor.go
+++ b/cryptor/cryptor.go
@@ -17,6 +17,7 @@ import (
"github.com/cloudflare/redoctober/keycache"
"github.com/cloudflare/redoctober/msp"
+ "github.com/cloudflare/redoctober/order"
"github.com/cloudflare/redoctober/padding"
"github.com/cloudflare/redoctober/passvault"
"github.com/cloudflare/redoctober/symcrypt"
@@ -29,10 +30,11 @@ const (
type Cryptor struct {
records *passvault.Records
cache *keycache.Cache
+ orders *order.Orderer
}
-func New(records *passvault.Records, cache *keycache.Cache) Cryptor {
- return Cryptor{records, cache}
+func New(records *passvault.Records, cache *keycache.Cache, orders *order.Orderer) Cryptor {
+ return Cryptor{records, cache, orders}
}
// AccessStructure represents different possible access structures for
@@ -508,14 +510,14 @@ func (c *Cryptor) Encrypt(in []byte, labels []string, access AccessStructure) (r
}
// Decrypt decrypts a file using the keys in the key cache.
-func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string, secure bool, err error) {
+func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, labels, names []string, secure bool, err error) {
// unwrap encrypted file
var encrypted EncryptedData
if err = json.Unmarshal(in, &encrypted); err != nil {
return
}
if encrypted.Version != DEFAULT_VERSION && encrypted.Version != -1 {
- return nil, nil, secure, errors.New("Unknown version")
+ return nil, nil, nil, secure, errors.New("Unknown version")
}
secure = encrypted.Version == -1
@@ -535,7 +537,7 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string,
return
}
if encrypted.VaultId != vaultId {
- return nil, nil, secure, errors.New("Wrong vault")
+ return nil, nil, nil, secure, errors.New("Wrong vault")
}
// compute HMAC
@@ -563,6 +565,7 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string,
aesCBC.CryptBlocks(clearData, encrypted.Data)
resp, err = padding.RemovePadding(clearData)
+ labels = encrypted.Labels
return
}
@@ -621,7 +624,6 @@ func (c *Cryptor) GetOwners(in []byte) (names []string, predicate string, err er
addedNames[name] = true
}
}
-
predicate = encrypted.Predicate
return
diff --git a/hipchat/hipchat.go b/hipchat/hipchat.go
new file mode 100644
index 0000000..0d1ddc2
--- /dev/null
+++ b/hipchat/hipchat.go
@@ -0,0 +1,67 @@
+package hipchat
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/url"
+ "strconv"
+)
+
+const (
+ RedBackground = "red"
+ YellowBackground = "yellow"
+ GreenBackground = "green"
+ GrayBackground = "gray"
+ RandomBackground = "random"
+ PurpleBackground = "purple"
+)
+
+type HipchatClient struct {
+ RoomId int
+ ApiKey string
+ HcHost string
+ RoHost string
+}
+
+func NewClient() *HipchatClient {
+ return &HipchatClient{}
+}
+func (h *HipchatClient) Notify(msg, color string) error {
+ if h.ApiKey == "" {
+ return errors.New("ApiKey unset")
+ }
+ msgBody := map[string]interface{}{
+ "message": msg,
+ "color": color,
+ "notify": true,
+ }
+
+ body, err := json.Marshal(msgBody)
+ if err != nil {
+ return err
+ }
+ roomId := url.QueryEscape(strconv.Itoa(h.RoomId))
+ hipchatUrl := fmt.Sprintf("https://%s/v2/room/%s/notification?auth_token=%s", h.HcHost, roomId, h.ApiKey)
+ req, err := http.NewRequest("POST", hipchatUrl, bytes.NewReader(body))
+
+ req.Header.Add("Content-Type", "application/json")
+
+ Client := http.Client{}
+ resp, err := Client.Do(req)
+ if err != nil {
+ log.Printf("Could not post to hipchat for the reason %s", err.Error())
+ return err
+ }
+
+ _, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Printf("Could not post to hipchat for the reason %s", err.Error())
+ return err
+ }
+ return nil
+}
diff --git a/index.html b/index.html
index ea9ef7e..a0c3f46 100644
--- a/index.html
+++ b/index.html
@@ -164,7 +164,7 @@
Change password
+ Change account
@@ -309,6 +311,93 @@