189 lines
4.6 KiB
Go
189 lines
4.6 KiB
Go
package rabbithole
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"math/rand"
|
|
"net/http"
|
|
)
|
|
|
|
type HashingAlgorithm string
|
|
|
|
func (algo HashingAlgorithm) String() string {
|
|
return string(algo)
|
|
}
|
|
|
|
const (
|
|
HashingAlgorithmSHA256 HashingAlgorithm = "rabbit_password_hashing_sha256"
|
|
HashingAlgorithmSHA512 HashingAlgorithm = "rabbit_password_hashing_sha512"
|
|
|
|
// deprecated, provided to support responses that include users created
|
|
// before RabbitMQ 3.6 and other legacy scenarios. MK.
|
|
HashingAlgorithmMD5 HashingAlgorithm = "rabbit_password_hashing_md5"
|
|
)
|
|
|
|
type UserInfo struct {
|
|
Name string `json:"name"`
|
|
PasswordHash string `json:"password_hash"`
|
|
HashingAlgorithm HashingAlgorithm `json:"hashing_algorithm,omitempty"`
|
|
// Tags control permissions. Built-in tags: administrator, management, policymaker.
|
|
Tags string `json:"tags"`
|
|
}
|
|
|
|
// Settings used to create users. Tags must be comma-separated.
|
|
type UserSettings struct {
|
|
Name string `json:"name"`
|
|
// Tags control permissions. Administrator grants full
|
|
// permissions, management grants management UI and HTTP API
|
|
// access, policymaker grants policy management permissions.
|
|
Tags string `json:"tags"`
|
|
|
|
// *never* returned by RabbitMQ. Set by the client
|
|
// to create/update a user. MK.
|
|
Password string `json:"password,omitempty"`
|
|
PasswordHash string `json:"password_hash,omitempty"`
|
|
HashingAlgorithm HashingAlgorithm `json:"hashing_algorithm,omitempty"`
|
|
}
|
|
|
|
//
|
|
// GET /api/users
|
|
//
|
|
|
|
// Example response:
|
|
// [{"name":"guest","password_hash":"8LYTIFbVUwi8HuV/dGlp2BYsD1I=","tags":"administrator"}]
|
|
|
|
// Returns a list of all users in a cluster.
|
|
func (c *Client) ListUsers() (rec []UserInfo, err error) {
|
|
req, err := newGETRequest(c, "users/")
|
|
if err != nil {
|
|
return []UserInfo{}, err
|
|
}
|
|
|
|
if err = executeAndParseRequest(c, req, &rec); err != nil {
|
|
return []UserInfo{}, err
|
|
}
|
|
|
|
return rec, nil
|
|
}
|
|
|
|
//
|
|
// GET /api/users/{name}
|
|
//
|
|
|
|
// Returns information about individual user.
|
|
func (c *Client) GetUser(username string) (rec *UserInfo, err error) {
|
|
req, err := newGETRequest(c, "users/"+PathEscape(username))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = executeAndParseRequest(c, req, &rec); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rec, nil
|
|
}
|
|
|
|
//
|
|
// PUT /api/users/{name}
|
|
//
|
|
|
|
// Updates information about individual user.
|
|
func (c *Client) PutUser(username string, info UserSettings) (res *http.Response, err error) {
|
|
body, err := json.Marshal(info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := newRequestWithBody(c, "PUT", "users/"+PathEscape(username), body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err = executeRequest(c, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (c *Client) PutUserWithoutPassword(username string, info UserSettings) (res *http.Response, err error) {
|
|
body, err := json.Marshal(UserInfo{Tags: info.Tags})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := newRequestWithBody(c, "PUT", "users/"+PathEscape(username), body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err = executeRequest(c, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
//
|
|
// DELETE /api/users/{name}
|
|
//
|
|
|
|
// Deletes user.
|
|
func (c *Client) DeleteUser(username string) (res *http.Response, err error) {
|
|
req, err := newRequestWithBody(c, "DELETE", "users/"+PathEscape(username), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err = executeRequest(c, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
//
|
|
// Password Hash generation
|
|
//
|
|
|
|
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
|
func GenerateSalt(n int) string {
|
|
bs := make([]byte, n)
|
|
for i := range bs {
|
|
bs[i] = characters[rand.Intn(len(characters))]
|
|
}
|
|
return string(bs)
|
|
}
|
|
|
|
func SaltedPasswordHashSHA256(password string) (string, string) {
|
|
salt := GenerateSalt(4)
|
|
hashed := sha256.Sum256([]byte(salt + password))
|
|
return salt, string(hashed[:])
|
|
}
|
|
|
|
// Produces a salted hash value expected by the HTTP API.
|
|
// See https://www.rabbitmq.com/passwords.html#computing-password-hash
|
|
// for details.
|
|
func Base64EncodedSaltedPasswordHashSHA256(password string) string {
|
|
salt, saltedHash := SaltedPasswordHashSHA256(password)
|
|
return base64.URLEncoding.EncodeToString([]byte(salt + saltedHash))
|
|
}
|
|
|
|
func SaltedPasswordHashSHA512(password string) (string, string) {
|
|
salt := GenerateSalt(4)
|
|
hashed := sha512.Sum512([]byte(salt + password))
|
|
return salt, string(hashed[:])
|
|
}
|
|
|
|
func Base64EncodedSaltedPasswordHashSHA512(password string) string {
|
|
salt, saltedHash := SaltedPasswordHashSHA512(password)
|
|
return base64.URLEncoding.EncodeToString([]byte(salt + saltedHash))
|
|
}
|