mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-07 22:15:34 +00:00
Merge pull request #106 from ejcx/ordermsp
Add hipchat and ordering support to redoctober
This commit is contained in:
81
README.md
81
README.md
@@ -247,6 +247,87 @@ Example input JSON format:
|
||||
-d '{"Name":"Alice","Password":"Lewis"}'
|
||||
{"Status":"ok"}
|
||||
|
||||
|
||||
### Order
|
||||
|
||||
Order creates a new order and lets other users know delegations are needed.
|
||||
|
||||
Example input JSON format:
|
||||
|
||||
$ curl --cacert server/server.crt https://localhost:8080/order \
|
||||
-d '{"Name":"Alice","Password":"Lewis","Labels": ["Blue","Red"],\
|
||||
"Duration":"1h","Uses":5,"EncryptedData":"ABCDE=="}'
|
||||
{
|
||||
"Admins": [
|
||||
"Bob",
|
||||
"Eve"
|
||||
],
|
||||
"AdminsDelegated": null,
|
||||
"Delegated": 0,
|
||||
"DurationRequested": 3.6e+12,
|
||||
"Labels": [
|
||||
"blue",
|
||||
"red"
|
||||
],
|
||||
"Name": "Alice",
|
||||
"Num": "77da1cfd8962fb9685c15c84",
|
||||
"TimeRequested": "2016-01-25T15:58:41.961906679-08:00",
|
||||
}
|
||||
|
||||
### Orders Outstanding
|
||||
|
||||
Orders Outstanding will return a list of current order numbers
|
||||
|
||||
Example input JSON format:
|
||||
|
||||
$ curl --cacert server/server.crt https://localhost:8080/orderout
|
||||
-d '{"Name":"Alice","Password":"Lewis"}'
|
||||
{
|
||||
"77da1cfd8962fb9685c15c84":{
|
||||
"Name":"Alice",
|
||||
"Num":"77da1cfd8962fb9685c15c84",
|
||||
"TimeRequested":"2016-01-25T15:58:41.961906679-08:00",
|
||||
"DurationRequested":3600000000000,
|
||||
"Delegated":0,"
|
||||
AdminsDelegated":null,
|
||||
"Admins":["Bob, Eve"],
|
||||
"Labels":["Blue","Red"]
|
||||
}
|
||||
}
|
||||
|
||||
### Order Information
|
||||
|
||||
Example input JSON format:
|
||||
|
||||
$ curl --cacert server/server.crt https://localhost:8080/orderinfo
|
||||
-d '{"Name":"Alice","Password":"Lewis", \
|
||||
"OrderNum":"77da1cfd8962fb9685c15c84"}'
|
||||
{
|
||||
"Admins": [
|
||||
"Bob",
|
||||
"Eve"
|
||||
],
|
||||
"AdminsDelegated": null,
|
||||
"Delegated": 0,
|
||||
"DurationRequested": 3.6e+12,
|
||||
"Labels": [
|
||||
"blue",
|
||||
"red"
|
||||
],
|
||||
"Name": "Alice",
|
||||
"Num": "77da1cfd8962fb9685c15c84",
|
||||
"TimeRequested": "2016-01-25T15:58:41.961906679-08:00"
|
||||
}
|
||||
|
||||
### Order Cancel
|
||||
|
||||
Example input JSON format:
|
||||
|
||||
$ curl --cacert server/server.crt https://localhost:8080/orderinfo
|
||||
-d '{"Name":"Alice","Password":"Lewis", \
|
||||
"OrderNum":"77da1cfd8962fb9685c15c84"}'
|
||||
{"Status":"ok"}
|
||||
|
||||
### Web interface
|
||||
|
||||
You can build a web interface to manage the Red October service using
|
||||
|
||||
@@ -283,3 +283,63 @@ func (c *RemoteServer) Password(req []byte) (*core.ResponseData, error) {
|
||||
|
||||
return unmarshalResponseData(respBytes)
|
||||
}
|
||||
|
||||
// Order issues an order request to the remote server
|
||||
func (c *RemoteServer) Order(req core.OrderRequest) (*core.ResponseData, error) {
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes, err := c.doAction("order", reqBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalResponseData(respBytes)
|
||||
}
|
||||
|
||||
// OrderOutstanding issues an order outstanding request to the remote server
|
||||
func (c *RemoteServer) OrderOutstanding(req core.OrderOutstandingRequest) (*core.ResponseData, error) {
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes, err := c.doAction("orderout", reqBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalResponseData(respBytes)
|
||||
}
|
||||
|
||||
// OrderInfo issues an order info request to the remote server
|
||||
func (c *RemoteServer) OrderInfo(req core.OrderInfoRequest) (*core.ResponseData, error) {
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes, err := c.doAction("orderinfo", reqBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalResponseData(respBytes)
|
||||
}
|
||||
|
||||
// OrderCancel issues an order cancel request to the remote server
|
||||
func (c *RemoteServer) OrderCancel(req core.OrderInfoRequest) (*core.ResponseData, error) {
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes, err := c.doAction("ordercancel", reqBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalResponseData(respBytes)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/redoctober/client"
|
||||
"github.com/cloudflare/redoctober/cmd/ro/gopass"
|
||||
"github.com/cloudflare/redoctober/core"
|
||||
"github.com/cloudflare/redoctober/order"
|
||||
)
|
||||
|
||||
var action, user, pswd, userEnv, pswdEnv, server, caPath string
|
||||
@@ -21,7 +23,9 @@ var owners, lefters, righters, inPath, labels, outPath, outEnv string
|
||||
|
||||
var uses int
|
||||
|
||||
var time, users string
|
||||
var duration, users string
|
||||
|
||||
var pollInterval time.Duration
|
||||
|
||||
type command struct {
|
||||
Run func()
|
||||
@@ -37,6 +41,7 @@ var commandSet = map[string]command{
|
||||
"encrypt": command{Run: runEncrypt, Desc: "encrypt a file"},
|
||||
"decrypt": command{Run: runDecrypt, Desc: "decrypt a file"},
|
||||
"re-encrypt": command{Run: runReEncrypt, Desc: "re-encrypt a file"},
|
||||
"order": command{Run: runOrder, Desc: "place an order for delegations"},
|
||||
}
|
||||
|
||||
func registerFlags() {
|
||||
@@ -45,7 +50,7 @@ func registerFlags() {
|
||||
flag.StringVar(&owners, "owners", "", "comma separated owner list")
|
||||
flag.StringVar(&users, "users", "", "comma separated user list")
|
||||
flag.IntVar(&uses, "uses", 0, "number of delegated key uses")
|
||||
flag.StringVar(&time, "time", "0h", "duration of delegated key uses")
|
||||
flag.StringVar(&duration, "time", "0h", "duration of delegated key uses")
|
||||
flag.StringVar(&lefters, "left", "", "comma separated left owners")
|
||||
flag.StringVar(&righters, "right", "", "comma separated right owners")
|
||||
flag.StringVar(&labels, "labels", "", "comma separated labels")
|
||||
@@ -56,6 +61,7 @@ func registerFlags() {
|
||||
flag.StringVar(&pswd, "password", "", "password")
|
||||
flag.StringVar(&userEnv, "userenv", "RO_USER", "env variable for user name")
|
||||
flag.StringVar(&pswdEnv, "pswdenv", "RO_PASS", "env variable for user password")
|
||||
flag.DurationVar(&pollInterval, "poll-interval", time.Second, "interval for polling an outstanding order (set 0 to disable polling)")
|
||||
}
|
||||
|
||||
func getUserCredentials() {
|
||||
@@ -99,7 +105,7 @@ func runDelegate() {
|
||||
Name: user,
|
||||
Password: pswd,
|
||||
Uses: uses,
|
||||
Time: time,
|
||||
Time: duration,
|
||||
Users: processCSL(users),
|
||||
Labels: processCSL(labels),
|
||||
}
|
||||
@@ -207,6 +213,34 @@ func runDecrypt() {
|
||||
ioutil.WriteFile(outPath, msg.Data, 0644)
|
||||
}
|
||||
|
||||
func runOrder() {
|
||||
req := core.OrderRequest{
|
||||
Name: user,
|
||||
Password: pswd,
|
||||
Uses: uses,
|
||||
Duration: duration,
|
||||
Labels: processCSL(labels),
|
||||
Users: processCSL(users),
|
||||
}
|
||||
resp, err := roServer.Order(req)
|
||||
processError(err)
|
||||
|
||||
var o order.Order
|
||||
err = json.Unmarshal(resp.Response, &o)
|
||||
processError(err)
|
||||
|
||||
if pollInterval > 0 {
|
||||
for o.Delegated < 2 {
|
||||
time.Sleep(pollInterval)
|
||||
resp, err = roServer.OrderInfo(core.OrderInfoRequest{Name: user, Password: pswd, OrderNum: o.Num})
|
||||
processError(err)
|
||||
err = json.Unmarshal(resp.Response, &o)
|
||||
processError(err)
|
||||
}
|
||||
}
|
||||
fmt.Println(resp.Status)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: ro [options] subcommand")
|
||||
|
||||
261
core/core.go
261
core/core.go
@@ -9,9 +9,13 @@ 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"
|
||||
)
|
||||
|
||||
@@ -19,6 +23,7 @@ var (
|
||||
crypt cryptor.Cryptor
|
||||
records passvault.Records
|
||||
cache keycache.Cache
|
||||
orders order.Orderer
|
||||
)
|
||||
|
||||
// Each of these structures corresponds to the JSON expected on the
|
||||
@@ -53,9 +58,10 @@ type DelegateRequest struct {
|
||||
}
|
||||
|
||||
type CreateUserRequest struct {
|
||||
Name string
|
||||
Password string
|
||||
UserType string
|
||||
Name string
|
||||
Password string
|
||||
UserType string
|
||||
HipchatName string
|
||||
}
|
||||
|
||||
type PasswordRequest struct {
|
||||
@@ -63,6 +69,7 @@ type PasswordRequest struct {
|
||||
Password string
|
||||
|
||||
NewPassword string
|
||||
HipchatName string
|
||||
}
|
||||
|
||||
type EncryptRequest struct {
|
||||
@@ -106,6 +113,34 @@ type ExportRequest struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
type OrderRequest struct {
|
||||
Name string
|
||||
Password string
|
||||
Duration string
|
||||
Uses int
|
||||
Users []string
|
||||
EncryptedData []byte
|
||||
Labels []string
|
||||
}
|
||||
|
||||
type OrderInfoRequest struct {
|
||||
Name string
|
||||
Password string
|
||||
|
||||
OrderNum string
|
||||
}
|
||||
type OrderOutstandingRequest struct {
|
||||
Name string
|
||||
Password string
|
||||
}
|
||||
|
||||
type OrderCancelRequest struct {
|
||||
Name string
|
||||
Password string
|
||||
|
||||
OrderNum string
|
||||
}
|
||||
|
||||
// These structures map the JSON responses that will be sent from the API
|
||||
|
||||
type ResponseData struct {
|
||||
@@ -169,8 +204,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")
|
||||
@@ -183,7 +217,7 @@ func validateName(name, password string) error {
|
||||
}
|
||||
|
||||
// Init reads the records from disk from a given path
|
||||
func Init(path string) error {
|
||||
func Init(path, hcKey, hcRoom, hcHost, roHost string) error {
|
||||
var err error
|
||||
|
||||
defer func() {
|
||||
@@ -198,6 +232,20 @@ func Init(path string) error {
|
||||
err = fmt.Errorf("failed to load password vault %s: %s", path, err)
|
||||
}
|
||||
|
||||
var hipchatClient hipchat.HipchatClient
|
||||
if hcKey != "" && hcRoom != "" && hcHost != "" {
|
||||
roomId, err := strconv.Atoi(hcRoom)
|
||||
if err != nil {
|
||||
return errors.New("core.init unable to use hipchat roomId provided")
|
||||
}
|
||||
hipchatClient = hipchat.HipchatClient{
|
||||
ApiKey: hcKey,
|
||||
RoomId: roomId,
|
||||
HcHost: hcHost,
|
||||
RoHost: roHost,
|
||||
}
|
||||
}
|
||||
orders = order.NewOrderer(hipchatClient)
|
||||
cache = keycache.Cache{UserKeys: make(map[keycache.DelegateIndex]keycache.ActiveUser)}
|
||||
crypt = cryptor.New(&records, &cache)
|
||||
|
||||
@@ -352,6 +400,32 @@ func Delegate(jsonIn []byte) ([]byte, error) {
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
// Make sure we capture the number who have already delegated.
|
||||
for _, delegatedUser := range s.Users {
|
||||
if orderKey, found := orders.FindOrder(delegatedUser, s.Labels); found {
|
||||
order := orders.Orders[orderKey]
|
||||
|
||||
// Don't re-add names to the list of people who have delegated. Instead
|
||||
// just skip them but make sure we count their delegation
|
||||
if len(order.OwnersDelegated) == 0 {
|
||||
order.OwnersDelegated = append(order.OwnersDelegated, s.Name)
|
||||
} else {
|
||||
for _, ownerName := range order.OwnersDelegated {
|
||||
if ownerName == s.Name {
|
||||
continue
|
||||
}
|
||||
order.OwnersDelegated = append(order.OwnersDelegated, s.Name)
|
||||
order.Delegated++
|
||||
}
|
||||
}
|
||||
orders.Orders[orderKey] = order
|
||||
|
||||
// Notify the hipchat room that there was a new delegator
|
||||
orders.NotifyDelegation(s.Name, delegatedUser, orderKey, s.Time, s.Labels)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return jsonStatusOk()
|
||||
}
|
||||
|
||||
@@ -393,10 +467,13 @@ func CreateUser(jsonIn []byte) ([]byte, error) {
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
if _, err = records.AddNewRecord(s.Name, s.Password, false, s.UserType); err != nil {
|
||||
if _, err := records.AddNewRecord(s.Name, s.Password, false, s.UserType); err != nil {
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
if err = records.ChangePassword(s.Name, s.Password, "", s.HipchatName); err != nil {
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
return jsonStatusOk()
|
||||
}
|
||||
|
||||
@@ -423,7 +500,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)
|
||||
}
|
||||
@@ -490,7 +567,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)
|
||||
}
|
||||
@@ -535,7 +612,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)
|
||||
}
|
||||
@@ -551,6 +628,11 @@ func Decrypt(jsonIn []byte) ([]byte, error) {
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
// Cleanup any orders that have been fulfilled and notify the room.
|
||||
if orderKey, found := orders.FindOrder(s.Name, allLabels); found {
|
||||
delete(orders.Orders, orderKey)
|
||||
orders.NotifyOrderFulfilled(s.Name, orderKey)
|
||||
}
|
||||
return jsonResponse(out)
|
||||
}
|
||||
|
||||
@@ -661,3 +743,162 @@ 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.EncryptedData)
|
||||
if err != nil {
|
||||
jsonStatusError(err)
|
||||
}
|
||||
if o.Duration == "" {
|
||||
err = errors.New("Duration required when placing an order.")
|
||||
jsonStatusError(err)
|
||||
}
|
||||
if o.Uses == 0 {
|
||||
err = errors.New("Number of required uses necessary when placing an order.")
|
||||
jsonStatusError(err)
|
||||
}
|
||||
cache.Refresh()
|
||||
orderNum := order.GenerateNum()
|
||||
|
||||
if len(o.Users) == 0 {
|
||||
err = errors.New("Must specify at least one user per order.")
|
||||
jsonStatusError(err)
|
||||
}
|
||||
adminsDelegated, numDelegated := cache.DelegateStatus(o.Users[0], o.Labels, owners)
|
||||
duration, err := time.ParseDuration(o.Duration)
|
||||
if err != nil {
|
||||
jsonStatusError(err)
|
||||
}
|
||||
currentTime := time.Now()
|
||||
ord := order.CreateOrder(o.Name,
|
||||
orderNum,
|
||||
currentTime,
|
||||
duration,
|
||||
adminsDelegated,
|
||||
owners,
|
||||
o.Users,
|
||||
o.Labels,
|
||||
numDelegated)
|
||||
orders.Orders[orderNum] = ord
|
||||
out, err = json.Marshal(ord)
|
||||
|
||||
// Get a map to any alternative name we want to notify
|
||||
altOwners := records.GetAltNamesFromName(orders.AlternateName, owners)
|
||||
|
||||
// Let everyone on hipchat know there is a new order.
|
||||
orders.NotifyNewOrder(o.Duration, orderNum, o.Users, o.Labels, o.Uses, 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 jsonStatusError(errors.New("No order with that number"))
|
||||
}
|
||||
|
||||
// OrderCancel will cancel an order given an order num
|
||||
func OrderCancel(jsonIn []byte) (out []byte, err error) {
|
||||
var o OrderCancelRequest
|
||||
|
||||
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 o.Name == ord.Creator {
|
||||
delete(orders.Orders, o.OrderNum)
|
||||
out = []byte("Successfully removed order")
|
||||
return jsonResponse(out)
|
||||
}
|
||||
}
|
||||
err = errors.New("Invalid Order Number")
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
func TestCreate(t *testing.T) {
|
||||
createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
respJson, err := Create(createJson)
|
||||
if err != nil {
|
||||
@@ -66,7 +66,7 @@ func TestSummary(t *testing.T) {
|
||||
t.Fatalf("Error in summary of account with no vault, %v", s.Status)
|
||||
}
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
// check for summary of initialized vault
|
||||
respJson, err = Create(createJson)
|
||||
@@ -220,7 +220,7 @@ func TestCreateUser(t *testing.T) {
|
||||
createUserECCJson := []byte("{\"Name\":\"Cat\",\"Password\":\"Cheshire\",\"UserType\":\"ECC\"}")
|
||||
createVaultJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}")
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
// Check that users cannot be created before a vault is
|
||||
respJson, err := CreateUser(createUserJson)
|
||||
@@ -292,7 +292,7 @@ func TestPassword(t *testing.T) {
|
||||
delegateJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"Time\":\"2h\",\"Uses\":1}")
|
||||
passwordJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"NewPassword\":\"Hello\"}")
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
// check for summary of initialized vault with new member
|
||||
var s ResponseData
|
||||
@@ -405,7 +405,7 @@ func TestEncryptDecrypt(t *testing.T) {
|
||||
encryptJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minimum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\",\"Labels\":[\"blue\",\"red\"]}")
|
||||
encryptJson3 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minimum\":1,\"Owners\":[\"Alice\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
// check for summary of initialized vault with new member
|
||||
var s ResponseData
|
||||
@@ -632,7 +632,7 @@ func TestReEncrypt(t *testing.T) {
|
||||
delegateJson7 := []byte(`{"Name":"Carol","Password":"Hello","Time":"10s","Uses":2,"Users":["Alice"],"Labels":["red"]}`)
|
||||
encryptJson := []byte(`{"Name":"Carol","Password":"Hello","Minimum":2,"Owners":["Alice","Bob","Carol"],"Data":"SGVsbG8gSmVsbG8=","Labels":["blue"]}`)
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
// check for summary of initialized vault with new member
|
||||
var s ResponseData
|
||||
@@ -812,7 +812,7 @@ func TestOwners(t *testing.T) {
|
||||
var s ResponseData
|
||||
var l OwnersData
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
Create(delegateJson)
|
||||
Delegate(delegateJson2)
|
||||
@@ -868,7 +868,7 @@ func TestModify(t *testing.T) {
|
||||
modifyJson4 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"revoke\"}")
|
||||
modifyJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"delete\"}")
|
||||
|
||||
Init("memory")
|
||||
Init("memory", "", "", "", "")
|
||||
|
||||
// check for summary of initialized vault with new member
|
||||
var s ResponseData
|
||||
@@ -1059,7 +1059,7 @@ func TestStatic(t *testing.T) {
|
||||
t.Fatalf("Error closing file, %v", err)
|
||||
}
|
||||
|
||||
Init("/tmp/db1.json")
|
||||
Init("/tmp/db1.json", "", "", "", "")
|
||||
|
||||
// check for summary of initialized vault with new member
|
||||
var s ResponseData
|
||||
|
||||
@@ -524,14 +524,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
|
||||
@@ -551,7 +551,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
|
||||
@@ -579,6 +579,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
|
||||
}
|
||||
|
||||
@@ -637,7 +638,6 @@ func (c *Cryptor) GetOwners(in []byte) (names []string, predicate string, err er
|
||||
addedNames[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
predicate = encrypted.Predicate
|
||||
|
||||
return
|
||||
|
||||
@@ -83,7 +83,6 @@ func TestDuplicates(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
c := Cryptor{&records, &cache}
|
||||
|
||||
for _, name := range names {
|
||||
@@ -113,7 +112,7 @@ func TestDuplicates(t *testing.T) {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
_, _, _, err := c.Decrypt(resp, name)
|
||||
_, _, _, _, err := c.Decrypt(resp, name)
|
||||
if err == nil {
|
||||
t.Fatalf("That shouldn't have worked!")
|
||||
}
|
||||
|
||||
67
hipchat/hipchat.go
Normal file
67
hipchat/hipchat.go
Normal file
@@ -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,
|
||||
"notify": "true",
|
||||
"color": color,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
372
index.html
372
index.html
@@ -27,6 +27,7 @@
|
||||
<li><a href="#encrypt">Encrypt</a></li>
|
||||
<li><a href="#decrypt">Decrypt</a></li>
|
||||
<li><a href="#owners">Owners</a></li>
|
||||
<li><a href="#orders">Order</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,6 +71,8 @@
|
||||
<label for="delegate-labels">Labels to allow <small>(comma separated)</small></label>
|
||||
<input type="text" name="Labels" class="form-control" id="delegate-labels" placeholder="e.g. Blue, Red" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="delegate-labels">Slot Name</label>
|
||||
<input type="text" name="Slot" class="form-control" id="delegate-slot" placeholder="Afternoon" />
|
||||
@@ -146,6 +149,12 @@
|
||||
<input type="password" name="Password" class="form-control" id="create-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="create-user-hipchatname">Hipchat Name</label>
|
||||
<input type="text" name="HipchatName" class="form-control" id="create-hipchatname" placeholder="HipchatName" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label for="create-user-type">User Type</label>
|
||||
@@ -164,7 +173,7 @@
|
||||
|
||||
<section class="row">
|
||||
<div id="change-password" class="col-md-6">
|
||||
<h3>Change password</h3>
|
||||
<h3>Change account</h3>
|
||||
|
||||
<form id="user-change-password" class="ro-user-change-password" role="form" action="/password" method="post">
|
||||
<div class="feedback change-password-feedback"></div>
|
||||
@@ -172,21 +181,25 @@
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="user-name">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="user-name" placeholder="User name" required />
|
||||
<input type="text" name="Name" class="form-control" id="user-name" placeholder="User name" required/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="user-pass">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="user-pass" placeholder="Password" required />
|
||||
<input type="password" name="Password" class="form-control" id="user-pass" placeholder="Password"/ required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-pass">New password</label>
|
||||
<input type="password" name="NewPassword" class="form-control" id="user-pass-new" placeholder="New password" required />
|
||||
<label for="user-pass">New password. Blank for no change.</label>
|
||||
<input type="password" name="NewPassword" class="form-control" id="user-pass-new" placeholder="New Password"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-email">Hipchat Name. Blank for no change.</label>
|
||||
<input type="text" name="HipchatName" class="form-control" id="user-hipchatname" placeholder="New Hipchat Name"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Change password</button>
|
||||
</form>
|
||||
|
||||
<h3>Modify user</h3>
|
||||
<h3>Admin Controls</h3>
|
||||
|
||||
<form id="user-modify" class="ro-user-modify" role="form" action="/modify" method="post">
|
||||
<div class="feedback modify-feedback"></div>
|
||||
@@ -309,6 +322,174 @@
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<section class="row">
|
||||
<div id="orders" class="col-md-6">
|
||||
<h3>Create Order</h3>
|
||||
|
||||
<form id="order" class="ro-user-order" role="form" action="/order" method="post">
|
||||
<div class="feedback order-feedback"></div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="order-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="order-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="order-user-pass">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="order-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="order-duration">Duration</label>
|
||||
<input type="text" name="Duration" class="form-control" id="order-duration" placeholder="Duration (e.g., 2h34m)" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="order-uses">Uses</label>
|
||||
<input type="number" name="Uses" class="form-control" id="order-uses" placeholder="5" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="order-name-users">Users to allow <small>(comma separated)</small></label>
|
||||
<input type="text" name="Users" class="form-control" id="order-name-users" placeholder="e.g. Alice, Bob" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="order-label">Labels</label>
|
||||
<input type="text" name="Labels" class="form-control" id="order-user-label" placeholder="Labels" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label for="owners-data">Encrypted Data</label>
|
||||
<textarea name="EncryptedData" class="form-control" id="owners-data" rows="5" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create Order</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<section class="row">
|
||||
<div id="ordersinfo" class="col-md-6">
|
||||
<h3>Order Info</h3>
|
||||
|
||||
<form id="orderinfo" class="ro-user-order" role="form" action="/orderinfo" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback orderinfo-feedback"></div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderinfo-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="orderinfo-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderinfo-user-admin">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="orderinfo-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderinfo-order-num">Order Number</label>
|
||||
<input type="text" name="OrderNum" class="form-control" id="orderinfo-user-label" placeholder="Order Number" required />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Order Info</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<section class="row">
|
||||
<div id="ordersout" class="col-md-6">
|
||||
<h3>Outstanding Orders</h3>
|
||||
|
||||
<form id="orderout" class="ro-user-order" role="form" action="/orderout" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback ordersout-feedback"></div>
|
||||
<div class="form-group">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="ordersout-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="ordersout-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="ordersout-user-admin">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="ordersout-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Outstanding Orders</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row">
|
||||
<div id="orderscancel" class="col-md-6">
|
||||
<h3>Order Cancel</h3>
|
||||
|
||||
<form id="ordercancel" class="ro-user-order" role="form" action="/ordercancel" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback ordercancel-feedback"></div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="ordercancel-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="ordercancel-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="ordercancel-user-admin">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="ordercancel-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="ordercancel-order-num">Order Number</label>
|
||||
<input type="text" name="OrderNum" class="form-control" id="ordercancel-user-label" placeholder="Order Number" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Order Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row">
|
||||
<div id="orderscancel" class="col-md-6">
|
||||
<h3>Create Delegation Link</h3>
|
||||
|
||||
<form id="orderlink" class="ro-orderlink" role="form" action="#" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback orderlink-feedback"></div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-delegator">Delegator</label>
|
||||
<input type="text" name="Name" class="form-control" id="orderlink-delegator" placeholder="User name"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-labels">Labels</label>
|
||||
<input type="text" name="labels" class="form-control" id="orderlink-labels" placeholder="Labels"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-duration">Duration</label>
|
||||
<input type="text" name="duration" class="form-control" id="orderlink-duration" placeholder="1h 5m"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-uses">Uses</label>
|
||||
<input type="text" name="uses" class="form-control" id="orderlink-uses" placeholder="5"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-ordernum">Order Number</label>
|
||||
<input type="text" name="ordernum" class="form-control" id="orderlink-ordernum" placeholder="d34db33f..."/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-delegatefor">Delegate For</label>
|
||||
<input type="text" name="delegatefor" class="form-control" id="orderlink-delegatefor" placeholder="e.g. Alice, Bob"/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create Link</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<footer id="footer" class="footer">
|
||||
@@ -327,7 +508,6 @@
|
||||
|
||||
function submit( $form, options ){
|
||||
options || (options = {});
|
||||
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
data: JSON.stringify( options.data ),
|
||||
@@ -364,7 +544,7 @@
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Created user: '+data.Name }) );
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Created user: '+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -425,7 +605,7 @@
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Delegating '+data.Name }) );
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Delegating '+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -442,7 +622,7 @@
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Creating '+data.Name }) );
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Creating '+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -456,7 +636,13 @@
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Change password for '+data.Name }) );
|
||||
var msg = "Change password for ";
|
||||
if (data.NewPassword != "" && data.HipchatName != "") {
|
||||
msg = "Change Password and Hipchat Name for ";
|
||||
} else if (data.NewPassword == "" && data.HipchatName != "") {
|
||||
msg = "Change Hipchat Name for ";
|
||||
}
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: msg+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -470,7 +656,7 @@
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Successfully modified '+data.ToModify }) );
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Successfully modified '+htmlspecialchars(data.ToModify) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -532,6 +718,168 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
// Create an order
|
||||
$('body').on('submit', 'form#order', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
// Force uses to an integer
|
||||
data.Uses = parseInt(data.Uses, 10);
|
||||
data.Labels = data.Labels.split(',');
|
||||
for(var i=0, l=data.Labels.length; i<l; i++){
|
||||
data.Labels[i] = data.Labels[i].trim();
|
||||
if (data.Labels[i] == "") { data.Labels.splice(i, 1); }
|
||||
}
|
||||
data.Users = data.Users.split(',');
|
||||
for(var i=0, l=data.Users.length; i<l; i++){
|
||||
data.Users[i] = data.Users[i].trim();
|
||||
if (data.Users[i] == "") { data.Users.splice(i, 1); }
|
||||
}
|
||||
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = JSON.parse(window.atob(d.Response));
|
||||
$form.find('.feedback').empty().append(
|
||||
makeAlert({ type: 'success', message: '<p>Order Number: '+d.Num+'</p>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
// Get order info
|
||||
$('body').on('submit', 'form#orderinfo', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = window.atob(d.Response);
|
||||
try {
|
||||
var respData = JSON.parse(d);
|
||||
var msgText = "";
|
||||
for (var jj in respData) {
|
||||
if (!jj)
|
||||
continue;
|
||||
if (!respData.hasOwnProperty(jj)) {
|
||||
continue;
|
||||
}
|
||||
if (typeof(respData[jj]) == "object") {
|
||||
msgText += "<p>"+htmlspecialchars(jj)+": "+htmlspecialchars(JSON.stringify(respData[jj]))+"</p>";
|
||||
} else {
|
||||
msgText += "<p>"+htmlspecialchars(jj)+": "+htmlspecialchars(respData[jj])+"</p>";
|
||||
}
|
||||
}
|
||||
$form.find('.feedback').empty().append(makeAlert({ type: 'success', message: msgText }));
|
||||
} catch (e) {
|
||||
makeAlert({ type: 'failure', message: '<p>Invalid JSON returned</p>' });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// Get outstanding order info
|
||||
$('body').on('submit', 'form#orderout', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = JSON.parse(window.atob(d.Response));
|
||||
ordout = "";
|
||||
for (var jj in d){
|
||||
if (!d.hasOwnProperty(jj))
|
||||
continue;
|
||||
var o = d[jj];
|
||||
ordout += o.Name + " requesting " + JSON.stringify(o.Labels) + " has " + o.Delegated + "\n";
|
||||
|
||||
}
|
||||
$form.find('.feedback').empty().append(
|
||||
makeAlert({ type: 'success', message: '<p>'+ordout+'</p>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
$('body').on('submit', 'form#ordercancel', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = window.atob(d.Response);
|
||||
$form.find('.feedback').empty().append(
|
||||
makeAlert({ type: 'success', message: '<p>'+d+'</p>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
$('body').on('submit', 'form#orderlink', function(evt){
|
||||
evt.preventDefault();
|
||||
createLink();
|
||||
});
|
||||
|
||||
// Init from query string if possible.
|
||||
var queryParams = document.location.search;
|
||||
var queryParts = queryParams.split('&');
|
||||
for (var i=0; i<queryParts.length; i++) {
|
||||
var part = queryParts[i];
|
||||
part = part.replace("?", "");
|
||||
var partPieces = part.split("=");
|
||||
if (partPieces.length != 2) {
|
||||
continue;
|
||||
}
|
||||
var setValue = null;
|
||||
var key = partPieces[0];
|
||||
var value = partPieces[1];
|
||||
switch (key) {
|
||||
case "delegator":
|
||||
setValue = $("#delegate-user");
|
||||
break;
|
||||
case "delegatee":
|
||||
setValue = $("#delegate-users");
|
||||
break;
|
||||
case "uses":
|
||||
setValue = $("#delegate-uses");
|
||||
break;
|
||||
case "label":
|
||||
setValue = $("#delegate-labels");
|
||||
break;
|
||||
case "duration":
|
||||
setValue = $("#delegate-user-time");
|
||||
break;
|
||||
case "ordernum":
|
||||
setValue = $("#delegate-slot");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (setValue) {
|
||||
setValue.val(decodeURIComponent(value));
|
||||
}
|
||||
}
|
||||
function createLink() {
|
||||
var delegator = decodeURIComponent(document.getElementById("orderlink-delegator").value);
|
||||
var delegatee = decodeURIComponent(document.getElementById("orderlink-delegatefor").value);
|
||||
var duration = decodeURIComponent(document.getElementById("orderlink-duration").value);
|
||||
var orderNum = decodeURIComponent(document.getElementById("orderlink-ordernum").value);
|
||||
var labels = decodeURIComponent(document.getElementById("orderlink-labels").value);
|
||||
var uses = decodeURIComponent(document.getElementById("orderlink-uses").value);
|
||||
|
||||
var link = "https://" + document.location.host + "?delegator="+ delegator + "&delegatee="+ delegatee + "&label=" + labels + "&ordernum=" + orderNum + "&uses=" + uses + "&duration="+ duration;
|
||||
$('.orderlink-feedback').empty().append(makeAlert({ type: 'success', message: '<p>'+htmlspecialchars(link)+'</p>' }) );
|
||||
}
|
||||
function htmlspecialchars(s) {
|
||||
if (!isNaN(s)) {
|
||||
return s;
|
||||
}
|
||||
s = s.replace('&', '&');
|
||||
s = s.replace('<', '<');
|
||||
s = s.replace('>', '>');
|
||||
s = s.replace('"', '"');
|
||||
s = s.replace("'", ''');
|
||||
s = s.replace('/', '/');
|
||||
return s
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -40,8 +40,9 @@ type Usage struct {
|
||||
// ActiveUser holds the information about an actively delegated key.
|
||||
type ActiveUser struct {
|
||||
Usage
|
||||
Admin bool
|
||||
Type string
|
||||
AltNames map[string]string
|
||||
Admin bool
|
||||
Type string
|
||||
|
||||
rsaKey rsa.PrivateKey
|
||||
eccKey *ecdsa.PrivateKey
|
||||
@@ -305,3 +306,34 @@ func (cache *Cache) DecryptShares(in [][]byte, name, user string, labels []strin
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DelegateStatus will return a list of admins who have delegated to a particular user, for a particular label.
|
||||
// This is useful information to have when determining the status of an order and conveying order progress.
|
||||
func (cache *Cache) DelegateStatus(name string, labels, admins []string) (adminsDelegated []string, hasDelegated int) {
|
||||
// Iterate over the admins of the ciphertext to look for users
|
||||
// who have already delegated the label to the delegatee.
|
||||
for _, admin := range admins {
|
||||
for di, use := range cache.UserKeys {
|
||||
if di.Name != admin {
|
||||
continue
|
||||
}
|
||||
nameFound := false
|
||||
for _, user := range use.Users {
|
||||
if user == name {
|
||||
nameFound = true
|
||||
}
|
||||
}
|
||||
for _, ol := range use.Labels {
|
||||
for _, il := range labels {
|
||||
if ol == il {
|
||||
if nameFound {
|
||||
adminsDelegated = append(adminsDelegated, admin)
|
||||
hasDelegated++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
168
order/order.go
Normal file
168
order/order.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Package order manages the bookkeeping and utilies required
|
||||
// for users to create an 'order' meaning they have requested
|
||||
// delegations for a certian resource.
|
||||
//
|
||||
// Copyright (c) 2016 CloudFlare, Inc.
|
||||
package order
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/redoctober/hipchat"
|
||||
)
|
||||
|
||||
const (
|
||||
NewOrder = "%s has created an order for the label %s. requesting %d delegations for %s"
|
||||
NewOrderLink = "@%s - https://%s?%s"
|
||||
OrderFulfilled = "%s has had order %s fulfilled."
|
||||
NewDelegation = "%s has delegated the label %s to %s (per order %s) for %s"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
Creator string
|
||||
Users []string
|
||||
Num string
|
||||
|
||||
TimeRequested time.Time
|
||||
DurationRequested time.Duration
|
||||
Delegated int
|
||||
OwnersDelegated []string
|
||||
Owners []string
|
||||
Labels []string
|
||||
}
|
||||
|
||||
type OrderIndex struct {
|
||||
OrderFor string
|
||||
|
||||
OrderId string
|
||||
OrderOwners []string
|
||||
}
|
||||
|
||||
// Orders represents a mapping of Order IDs to Orders. This structure
|
||||
// is useful for looking up information about individual Orders and
|
||||
// whether or not an order has been fulfilled. Orders that have been
|
||||
// fulfilled will be removed from the structure.
|
||||
type Orderer struct {
|
||||
Orders map[string]Order
|
||||
Hipchat hipchat.HipchatClient
|
||||
AlternateName string
|
||||
}
|
||||
|
||||
func CreateOrder(name, orderNum string, time time.Time, duration time.Duration, adminsDelegated, contacts, users, labels []string, numDelegated int) (ord Order) {
|
||||
ord.Creator = name
|
||||
ord.Num = orderNum
|
||||
ord.Labels = labels
|
||||
ord.TimeRequested = time
|
||||
ord.DurationRequested = duration
|
||||
ord.OwnersDelegated = adminsDelegated
|
||||
ord.Owners = contacts
|
||||
ord.Delegated = numDelegated
|
||||
ord.Users = users
|
||||
return
|
||||
}
|
||||
|
||||
func GenerateNum() (num string) {
|
||||
b := make([]byte, 12)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// NewOrder will create a new map of Orders
|
||||
func NewOrderer(hipchatClient hipchat.HipchatClient) (o Orderer) {
|
||||
o.Orders = make(map[string]Order)
|
||||
o.Hipchat = hipchatClient
|
||||
o.AlternateName = "HipchatName"
|
||||
return
|
||||
}
|
||||
|
||||
// notify is a generic function for using a notifier, but it checks to make
|
||||
// sure that there is a notifier available, since there won't always be.
|
||||
func notify(o *Orderer, msg, color string) {
|
||||
o.Hipchat.Notify(msg, color)
|
||||
}
|
||||
func (o *Orderer) NotifyNewOrder(duration, orderNum string, names, labels []string, uses int, owners map[string]string) {
|
||||
labelList := ""
|
||||
for i, label := range labels {
|
||||
if i == 0 {
|
||||
labelList += label
|
||||
} else {
|
||||
// Never include spaces in something go URI encodes. Go will
|
||||
// add a + to the string, instead of a %20
|
||||
labelList += "," + label
|
||||
}
|
||||
}
|
||||
nameList := ""
|
||||
for i, name := range names {
|
||||
if i == 0 {
|
||||
nameList += name
|
||||
} else {
|
||||
// Never include spaces in something go URI encodes. Go will
|
||||
// add a + to the string, instead of a %20
|
||||
nameList += "," + name
|
||||
}
|
||||
}
|
||||
|
||||
n := fmt.Sprintf(NewOrder, nameList, labelList, uses, duration)
|
||||
notify(o, n, hipchat.RedBackground)
|
||||
for owner, hipchatName := range owners {
|
||||
queryParams := url.Values{
|
||||
"delegator": {owner},
|
||||
"label": {labelList},
|
||||
"duration": {duration},
|
||||
"uses": {strconv.Itoa(uses)},
|
||||
"ordernum": {orderNum},
|
||||
"delegatee": {nameList},
|
||||
}.Encode()
|
||||
notify(o, fmt.Sprintf(NewOrderLink, hipchatName, o.Hipchat.RoHost, queryParams), hipchat.GreenBackground)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Orderer) NotifyDelegation(delegator, delegatee, orderNum, duration string, labels []string) {
|
||||
labelList := ""
|
||||
for i, label := range labels {
|
||||
if i == 0 {
|
||||
labelList += label
|
||||
} else {
|
||||
labelList += ", " + label
|
||||
}
|
||||
}
|
||||
n := fmt.Sprintf(NewDelegation, delegator, labelList, delegatee, orderNum, duration)
|
||||
notify(o, n, hipchat.YellowBackground)
|
||||
}
|
||||
func (o *Orderer) NotifyOrderFulfilled(name, orderNum string) {
|
||||
n := fmt.Sprintf(OrderFulfilled, name, orderNum)
|
||||
notify(o, n, hipchat.PurpleBackground)
|
||||
}
|
||||
|
||||
func (o *Orderer) FindOrder(user string, labels []string) (string, bool) {
|
||||
for key, order := range o.Orders {
|
||||
foundLabel := false
|
||||
foundUser := false
|
||||
for _, orderUser := range order.Users {
|
||||
if orderUser == user {
|
||||
foundUser = true
|
||||
}
|
||||
}
|
||||
if !foundUser {
|
||||
continue
|
||||
}
|
||||
for _, ol := range order.Labels {
|
||||
foundLabel = false
|
||||
for _, il := range labels {
|
||||
if il == ol {
|
||||
foundLabel = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundLabel {
|
||||
continue
|
||||
}
|
||||
return key, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -85,7 +85,8 @@ type PasswordRecord struct {
|
||||
ECPrivIV []byte
|
||||
ECPublic ECPublicKey
|
||||
}
|
||||
Admin bool
|
||||
AltNames map[string]string
|
||||
Admin bool
|
||||
}
|
||||
|
||||
// diskRecords is the structure used to read and write a JSON file
|
||||
@@ -187,6 +188,8 @@ func createPasswordRec(password string, admin bool, userType string) (newRec Pas
|
||||
return
|
||||
}
|
||||
|
||||
newRec.AltNames = make(map[string]string)
|
||||
|
||||
// generate a key pair
|
||||
switch userType {
|
||||
case RSARecord:
|
||||
@@ -273,7 +276,6 @@ func InitFrom(path string) (records Records, err error) {
|
||||
// from the file.
|
||||
|
||||
records.Version = 0
|
||||
|
||||
if len(jsonDiskRecord) != 0 {
|
||||
if err = json.Unmarshal(jsonDiskRecord, &records); err != nil {
|
||||
return
|
||||
@@ -281,7 +283,12 @@ func InitFrom(path string) (records Records, err error) {
|
||||
}
|
||||
|
||||
err = errors.New("Format error")
|
||||
for _, rec := range records.Passwords {
|
||||
for k, rec := range records.Passwords {
|
||||
|
||||
if rec.AltNames == nil {
|
||||
rec.AltNames = make(map[string]string)
|
||||
records.Passwords[k] = rec
|
||||
}
|
||||
if len(rec.PasswordSalt) != 16 {
|
||||
return
|
||||
}
|
||||
@@ -364,13 +371,23 @@ func (records *Records) AddNewRecord(name, password string, admin bool, userType
|
||||
}
|
||||
|
||||
// ChangePassword changes the password for a given user.
|
||||
func (records *Records) ChangePassword(name, password, newPassword string) (err error) {
|
||||
func (records *Records) ChangePassword(name, password, newPassword, hipchatName string) (err error) {
|
||||
pr, ok := records.GetRecord(name)
|
||||
|
||||
if !ok {
|
||||
err = errors.New("Record not present")
|
||||
return
|
||||
}
|
||||
|
||||
if len(hipchatName) != 0 {
|
||||
pr.AltNames["HipchatName"] = hipchatName
|
||||
}
|
||||
|
||||
if len(newPassword) == 0 {
|
||||
records.SetRecord(pr, name)
|
||||
return records.WriteRecordsToDisk()
|
||||
}
|
||||
|
||||
var keySalt []byte
|
||||
if keySalt, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
@@ -609,6 +626,26 @@ func (pr *PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err er
|
||||
|
||||
return
|
||||
}
|
||||
func (r *Records) GetAltNameFromName(alt, name string) (altName string, found bool) {
|
||||
if passwordRecord, ok := r.Passwords[name]; ok {
|
||||
if altName, ok := passwordRecord.AltNames[alt]; ok {
|
||||
return altName, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
func (r *Records) GetAltNamesFromName(alt string, names []string) map[string]string {
|
||||
altNames := make(map[string]string)
|
||||
for _, name := range names {
|
||||
altName, found := r.GetAltNameFromName(alt, name)
|
||||
if !found {
|
||||
altName = name
|
||||
}
|
||||
altNames[name] = altName
|
||||
}
|
||||
return altNames
|
||||
|
||||
}
|
||||
|
||||
// ValidatePassword returns an error if the password is incorrect.
|
||||
func (pr *PasswordRecord) ValidatePassword(password string) error {
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestChangePassword(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check changing the password for a non-existent user
|
||||
err = records.ChangePassword("user", "weakpassword", "newpassword")
|
||||
err = records.ChangePassword("user", "weakpassword", "newpassword", "")
|
||||
if err == nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func TestChangePassword(t *testing.T) {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
err = records.ChangePassword("user", "weakpassword", "newpassword")
|
||||
err = records.ChangePassword("user", "weakpassword", "newpassword", "")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestChangePassword(t *testing.T) {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
err = records.ChangePassword("user2", "weakpassword", "newpassword")
|
||||
err = records.ChangePassword("user2", "weakpassword", "newpassword", "")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
390
redoctober.go
390
redoctober.go
@@ -29,10 +29,10 @@ import (
|
||||
|
||||
var functions = map[string]func([]byte) ([]byte, error){
|
||||
"/create": core.Create,
|
||||
"/create-user": core.CreateUser,
|
||||
"/summary": core.Summary,
|
||||
"/purge": core.Purge,
|
||||
"/delegate": core.Delegate,
|
||||
"/create-user": core.CreateUser,
|
||||
"/password": core.Password,
|
||||
"/encrypt": core.Encrypt,
|
||||
"/re-encrypt": core.ReEncrypt,
|
||||
@@ -40,6 +40,10 @@ var functions = map[string]func([]byte) ([]byte, error){
|
||||
"/owners": core.Owners,
|
||||
"/modify": core.Modify,
|
||||
"/export": core.Export,
|
||||
"/order": core.Order,
|
||||
"/orderout": core.OrdersOutstanding,
|
||||
"/orderinfo": core.OrderInfo,
|
||||
"/ordercancel": core.OrderCancel,
|
||||
}
|
||||
|
||||
type userRequest struct {
|
||||
@@ -200,9 +204,9 @@ const usage = `Usage:
|
||||
redoctober -static <path> -vaultpath <path> -addr <addr> -certs <path1>[,<path2>,...] -keys <path1>[,<path2>,...] [-ca <path>]
|
||||
|
||||
single-cert example:
|
||||
redoctober -vaultpath diskrecord.json -addr localhost:8080 -certs cert.pem -keys cert.key
|
||||
redoctober -vaultpath diskrecord.json -addr localhost:8081 -certs cert.pem -keys cert.key
|
||||
multi-cert example:
|
||||
redoctober -vaultpath diskrecord.json -addr localhost:8080 -certs cert1.pem,cert2.pem -keys cert1.key,cert2.key
|
||||
redoctober -vaultpath diskrecord.json -addr localhost:8081 -certs cert1.pem,cert2.pem -keys cert1.key,cert2.key
|
||||
`
|
||||
|
||||
func main() {
|
||||
@@ -215,11 +219,15 @@ func main() {
|
||||
|
||||
var staticPath = flag.String("static", "", "Path to override built-in index.html")
|
||||
var vaultPath = flag.String("vaultpath", "diskrecord.json", "Path to the the disk vault")
|
||||
var addr = flag.String("addr", "localhost:8080", "Server and port separated by :")
|
||||
var addr = flag.String("addr", "localhost:8081", "Server and port separated by :")
|
||||
var useSystemdSocket = flag.Bool("systemdfds", false, "Use systemd socket activation to listen on a file. Useful for binding privileged sockets.")
|
||||
var certsPathString = flag.String("certs", "", "Path(s) of TLS certificate in PEM format, comma-separated")
|
||||
var keysPathString = flag.String("keys", "", "Path(s) of TLS private key in PEM format, comma-separated, must me in the same order as the certs")
|
||||
var caPath = flag.String("ca", "", "Path of TLS CA for client authentication (optional)")
|
||||
var hcKey = flag.String("hckey", "", "Hipchat API Key")
|
||||
var hcRoom = flag.String("hcroom", "", "Hipchat Room Id")
|
||||
var hcHost = flag.String("hchost", "", "Hipchat Url Base (ex: hipchat.com)")
|
||||
var roHost = flag.String("rohost", "", "RedOctober Url Base (ex: localhost:8081)")
|
||||
flag.Parse()
|
||||
|
||||
if *vaultPath == "" || *certsPathString == "" || *keysPathString == "" || (*addr == "" && *useSystemdSocket == false) {
|
||||
@@ -231,7 +239,7 @@ func main() {
|
||||
certPaths := strings.Split(*certsPathString, ",")
|
||||
keyPaths := strings.Split(*keysPathString, ",")
|
||||
|
||||
if err := core.Init(*vaultPath); err != nil {
|
||||
if err := core.Init(*vaultPath, *hcKey, *hcRoom, *hcHost, *roHost); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
@@ -301,6 +309,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
<li><a href="#encrypt">Encrypt</a></li>
|
||||
<li><a href="#decrypt">Decrypt</a></li>
|
||||
<li><a href="#owners">Owners</a></li>
|
||||
<li><a href="#orders">Order</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -344,6 +353,8 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
<label for="delegate-labels">Labels to allow <small>(comma separated)</small></label>
|
||||
<input type="text" name="Labels" class="form-control" id="delegate-labels" placeholder="e.g. Blue, Red" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="delegate-labels">Slot Name</label>
|
||||
<input type="text" name="Slot" class="form-control" id="delegate-slot" placeholder="Afternoon" />
|
||||
@@ -420,6 +431,12 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
<input type="password" name="Password" class="form-control" id="create-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="create-user-hipchatname">Hipchat Name</label>
|
||||
<input type="text" name="HipchatName" class="form-control" id="create-hipchatname" placeholder="HipchatName" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label for="create-user-type">User Type</label>
|
||||
@@ -438,7 +455,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
|
||||
<section class="row">
|
||||
<div id="change-password" class="col-md-6">
|
||||
<h3>Change password</h3>
|
||||
<h3>Change account</h3>
|
||||
|
||||
<form id="user-change-password" class="ro-user-change-password" role="form" action="/password" method="post">
|
||||
<div class="feedback change-password-feedback"></div>
|
||||
@@ -446,21 +463,25 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="user-name">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="user-name" placeholder="User name" required />
|
||||
<input type="text" name="Name" class="form-control" id="user-name" placeholder="User name" required/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="user-pass">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="user-pass" placeholder="Password" required />
|
||||
<input type="password" name="Password" class="form-control" id="user-pass" placeholder="Password"/ required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-pass">New password</label>
|
||||
<input type="password" name="NewPassword" class="form-control" id="user-pass-new" placeholder="New password" required />
|
||||
<label for="user-pass">New password. Blank for no change.</label>
|
||||
<input type="password" name="NewPassword" class="form-control" id="user-pass-new" placeholder="New Password"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-email">Hipchat Name. Blank for no change.</label>
|
||||
<input type="text" name="HipchatName" class="form-control" id="user-hipchatname" placeholder="New Hipchat Name"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Change password</button>
|
||||
</form>
|
||||
|
||||
<h3>Modify user</h3>
|
||||
<h3>Admin Controls</h3>
|
||||
|
||||
<form id="user-modify" class="ro-user-modify" role="form" action="/modify" method="post">
|
||||
<div class="feedback modify-feedback"></div>
|
||||
@@ -583,6 +604,174 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<section class="row">
|
||||
<div id="orders" class="col-md-6">
|
||||
<h3>Create Order</h3>
|
||||
|
||||
<form id="order" class="ro-user-order" role="form" action="/order" method="post">
|
||||
<div class="feedback order-feedback"></div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="order-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="order-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="order-user-pass">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="order-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="order-duration">Duration</label>
|
||||
<input type="text" name="Duration" class="form-control" id="order-duration" placeholder="Duration (e.g., 2h34m)" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="order-uses">Uses</label>
|
||||
<input type="number" name="Uses" class="form-control" id="order-uses" placeholder="5" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="order-name-users">Users to allow <small>(comma separated)</small></label>
|
||||
<input type="text" name="Users" class="form-control" id="order-name-users" placeholder="e.g. Alice, Bob" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="order-label">Labels</label>
|
||||
<input type="text" name="Labels" class="form-control" id="order-user-label" placeholder="Labels" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label for="owners-data">Encrypted Data</label>
|
||||
<textarea name="EncryptedData" class="form-control" id="owners-data" rows="5" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create Order</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<section class="row">
|
||||
<div id="ordersinfo" class="col-md-6">
|
||||
<h3>Order Info</h3>
|
||||
|
||||
<form id="orderinfo" class="ro-user-order" role="form" action="/orderinfo" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback orderinfo-feedback"></div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderinfo-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="orderinfo-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderinfo-user-admin">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="orderinfo-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderinfo-order-num">Order Number</label>
|
||||
<input type="text" name="OrderNum" class="form-control" id="orderinfo-user-label" placeholder="Order Number" required />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Order Info</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
<section class="row">
|
||||
<div id="ordersout" class="col-md-6">
|
||||
<h3>Outstanding Orders</h3>
|
||||
|
||||
<form id="orderout" class="ro-user-order" role="form" action="/orderout" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback ordersout-feedback"></div>
|
||||
<div class="form-group">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="ordersout-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="ordersout-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="ordersout-user-admin">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="ordersout-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Outstanding Orders</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row">
|
||||
<div id="orderscancel" class="col-md-6">
|
||||
<h3>Order Cancel</h3>
|
||||
|
||||
<form id="ordercancel" class="ro-user-order" role="form" action="/ordercancel" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback ordercancel-feedback"></div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="ordercancel-user-admin">User name</label>
|
||||
<input type="text" name="Name" class="form-control" id="ordercancel-user-admin" placeholder="User name" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="ordercancel-user-admin">Password</label>
|
||||
<input type="password" name="Password" class="form-control" id="ordercancel-user-pass" placeholder="Password" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="ordercancel-order-num">Order Number</label>
|
||||
<input type="text" name="OrderNum" class="form-control" id="ordercancel-user-label" placeholder="Order Number" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Order Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row">
|
||||
<div id="orderscancel" class="col-md-6">
|
||||
<h3>Create Delegation Link</h3>
|
||||
|
||||
<form id="orderlink" class="ro-orderlink" role="form" action="#" method="post">
|
||||
<div style="overflow-wrap: break-word;" class="feedback orderlink-feedback"></div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-delegator">Delegator</label>
|
||||
<input type="text" name="Name" class="form-control" id="orderlink-delegator" placeholder="User name"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-labels">Labels</label>
|
||||
<input type="text" name="labels" class="form-control" id="orderlink-labels" placeholder="Labels"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-duration">Duration</label>
|
||||
<input type="text" name="duration" class="form-control" id="orderlink-duration" placeholder="1h 5m"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-uses">Uses</label>
|
||||
<input type="text" name="uses" class="form-control" id="orderlink-uses" placeholder="5"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-ordernum">Order Number</label>
|
||||
<input type="text" name="ordernum" class="form-control" id="orderlink-ordernum" placeholder="d34db33f..."/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="orderlink-delegatefor">Delegate For</label>
|
||||
<input type="text" name="delegatefor" class="form-control" id="orderlink-delegatefor" placeholder="e.g. Alice, Bob"/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create Link</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<footer id="footer" class="footer">
|
||||
@@ -601,7 +790,6 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
|
||||
function submit( $form, options ){
|
||||
options || (options = {});
|
||||
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
data: JSON.stringify( options.data ),
|
||||
@@ -638,7 +826,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Created user: '+data.Name }) );
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Created user: '+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -699,7 +887,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Delegating '+data.Name }) );
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Delegating '+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -716,7 +904,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Creating '+data.Name }) );
|
||||
$form.find('.feedback').append( makeAlert({ type: 'success', message: 'Creating '+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -730,7 +918,13 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Change password for '+data.Name }) );
|
||||
var msg = "Change password for ";
|
||||
if (data.NewPassword != "" && data.HipchatName != "") {
|
||||
msg = "Change Password and Hipchat Name for ";
|
||||
} else if (data.NewPassword == "" && data.HipchatName != "") {
|
||||
msg = "Change Hipchat Name for ";
|
||||
}
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: msg+htmlspecialchars(data.Name) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -744,7 +938,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Successfully modified '+data.ToModify }) );
|
||||
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: 'Successfully modified '+htmlspecialchars(data.ToModify) }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -806,6 +1000,168 @@ var indexHtml = []byte(`<!DOCTYPE html>
|
||||
}
|
||||
});
|
||||
});
|
||||
// Create an order
|
||||
$('body').on('submit', 'form#order', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
// Force uses to an integer
|
||||
data.Uses = parseInt(data.Uses, 10);
|
||||
data.Labels = data.Labels.split(',');
|
||||
for(var i=0, l=data.Labels.length; i<l; i++){
|
||||
data.Labels[i] = data.Labels[i].trim();
|
||||
if (data.Labels[i] == "") { data.Labels.splice(i, 1); }
|
||||
}
|
||||
data.Users = data.Users.split(',');
|
||||
for(var i=0, l=data.Users.length; i<l; i++){
|
||||
data.Users[i] = data.Users[i].trim();
|
||||
if (data.Users[i] == "") { data.Users.splice(i, 1); }
|
||||
}
|
||||
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = JSON.parse(window.atob(d.Response));
|
||||
$form.find('.feedback').empty().append(
|
||||
makeAlert({ type: 'success', message: '<p>Order Number: '+d.Num+'</p>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
// Get order info
|
||||
$('body').on('submit', 'form#orderinfo', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = window.atob(d.Response);
|
||||
try {
|
||||
var respData = JSON.parse(d);
|
||||
var msgText = "";
|
||||
for (var jj in respData) {
|
||||
if (!jj)
|
||||
continue;
|
||||
if (!respData.hasOwnProperty(jj)) {
|
||||
continue;
|
||||
}
|
||||
if (typeof(respData[jj]) == "object") {
|
||||
msgText += "<p>"+htmlspecialchars(jj)+": "+htmlspecialchars(JSON.stringify(respData[jj]))+"</p>";
|
||||
} else {
|
||||
msgText += "<p>"+htmlspecialchars(jj)+": "+htmlspecialchars(respData[jj])+"</p>";
|
||||
}
|
||||
}
|
||||
$form.find('.feedback').empty().append(makeAlert({ type: 'success', message: msgText }));
|
||||
} catch (e) {
|
||||
makeAlert({ type: 'failure', message: '<p>Invalid JSON returned</p>' });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// Get outstanding order info
|
||||
$('body').on('submit', 'form#orderout', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = JSON.parse(window.atob(d.Response));
|
||||
ordout = "";
|
||||
for (var jj in d){
|
||||
if (!d.hasOwnProperty(jj))
|
||||
continue;
|
||||
var o = d[jj];
|
||||
ordout += o.Name + " requesting " + JSON.stringify(o.Labels) + " has " + o.Delegated + "\n";
|
||||
|
||||
}
|
||||
$form.find('.feedback').empty().append(
|
||||
makeAlert({ type: 'success', message: '<p>'+ordout+'</p>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
$('body').on('submit', 'form#ordercancel', function(evt){
|
||||
evt.preventDefault();
|
||||
var $form = $(evt.currentTarget),
|
||||
data = serialize($form);
|
||||
|
||||
submit( $form, {
|
||||
data : data,
|
||||
success : function(d){
|
||||
d = window.atob(d.Response);
|
||||
$form.find('.feedback').empty().append(
|
||||
makeAlert({ type: 'success', message: '<p>'+d+'</p>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
$('body').on('submit', 'form#orderlink', function(evt){
|
||||
evt.preventDefault();
|
||||
createLink();
|
||||
});
|
||||
|
||||
// Init from query string if possible.
|
||||
var queryParams = document.location.search;
|
||||
var queryParts = queryParams.split('&');
|
||||
for (var i=0; i<queryParts.length; i++) {
|
||||
var part = queryParts[i];
|
||||
part = part.replace("?", "");
|
||||
var partPieces = part.split("=");
|
||||
if (partPieces.length != 2) {
|
||||
continue;
|
||||
}
|
||||
var setValue = null;
|
||||
var key = partPieces[0];
|
||||
var value = partPieces[1];
|
||||
switch (key) {
|
||||
case "delegator":
|
||||
setValue = $("#delegate-user");
|
||||
break;
|
||||
case "delegatee":
|
||||
setValue = $("#delegate-users");
|
||||
break;
|
||||
case "uses":
|
||||
setValue = $("#delegate-uses");
|
||||
break;
|
||||
case "label":
|
||||
setValue = $("#delegate-labels");
|
||||
break;
|
||||
case "duration":
|
||||
setValue = $("#delegate-user-time");
|
||||
break;
|
||||
case "ordernum":
|
||||
setValue = $("#delegate-slot");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (setValue) {
|
||||
setValue.val(decodeURIComponent(value));
|
||||
}
|
||||
}
|
||||
function createLink() {
|
||||
var delegator = decodeURIComponent(document.getElementById("orderlink-delegator").value);
|
||||
var delegatee = decodeURIComponent(document.getElementById("orderlink-delegatefor").value);
|
||||
var duration = decodeURIComponent(document.getElementById("orderlink-duration").value);
|
||||
var orderNum = decodeURIComponent(document.getElementById("orderlink-ordernum").value);
|
||||
var labels = decodeURIComponent(document.getElementById("orderlink-labels").value);
|
||||
var uses = decodeURIComponent(document.getElementById("orderlink-uses").value);
|
||||
|
||||
var link = "https://" + document.location.host + "?delegator="+ delegator + "&delegatee="+ delegatee + "&label=" + labels + "&ordernum=" + orderNum + "&uses=" + uses + "&duration="+ duration;
|
||||
$('.orderlink-feedback').empty().append(makeAlert({ type: 'success', message: '<p>'+htmlspecialchars(link)+'</p>' }) );
|
||||
}
|
||||
function htmlspecialchars(s) {
|
||||
if (!isNaN(s)) {
|
||||
return s;
|
||||
}
|
||||
s = s.replace('&', '&');
|
||||
s = s.replace('<', '<');
|
||||
s = s.replace('>', '>');
|
||||
s = s.replace('"', '"');
|
||||
s = s.replace("'", ''');
|
||||
s = s.replace('/', '/');
|
||||
return s
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user