Enhance auth JSONRPC, now provides persistent output

Implements

   - Auth.Generate("user")
   - Auth.Fetch("user")
   - Auth.Reset("user")

This patch also adds testing for each of these cases
This commit is contained in:
Harshavardhana 2015-09-18 02:59:57 -07:00
parent 51652c38cb
commit f8bb85aeb7
6 changed files with 222 additions and 26 deletions

View File

@ -17,6 +17,7 @@
package auth
import (
"os"
"os/user"
"path/filepath"
@ -37,7 +38,7 @@ type Config struct {
Users map[string]*User
}
// getAuthConfigPath get donut config file path
// getAuthConfigPath get users config path
func getAuthConfigPath() (string, *probe.Error) {
if customConfigPath != "" {
return customConfigPath, nil
@ -46,10 +47,51 @@ func getAuthConfigPath() (string, *probe.Error) {
if err != nil {
return "", probe.NewError(err)
}
authConfigPath := filepath.Join(u.HomeDir, ".minio", "users.json")
authConfigPath := filepath.Join(u.HomeDir, ".minio")
return authConfigPath, nil
}
// createAuthConfigPath create users config path
func createAuthConfigPath() *probe.Error {
authConfigPath, err := getAuthConfigPath()
if err != nil {
return err.Trace()
}
if err := os.MkdirAll(authConfigPath, 0700); err != nil {
return probe.NewError(err)
}
return nil
}
// isAuthConfigFileExists is auth config file exists?
func isAuthConfigFileExists() bool {
if _, err := os.Stat(mustGetAuthConfigFile()); err != nil {
if os.IsNotExist(err) {
return false
}
panic(err)
}
return true
}
// mustGetAuthConfigFile always get users config file, if not panic
func mustGetAuthConfigFile() string {
authConfigFile, err := getAuthConfigFile()
if err != nil {
panic(err)
}
return authConfigFile
}
// getAuthConfigFile get users config file
func getAuthConfigFile() (string, *probe.Error) {
authConfigPath, err := getAuthConfigPath()
if err != nil {
return "", err.Trace()
}
return filepath.Join(authConfigPath, "users.json"), nil
}
// customConfigPath not accessed from outside only allowed through get/set methods
var customConfigPath string
@ -58,9 +100,9 @@ func SetAuthConfigPath(configPath string) {
customConfigPath = configPath
}
// SaveConfig save donut config
// SaveConfig save auth config
func SaveConfig(a *Config) *probe.Error {
authConfigPath, err := getAuthConfigPath()
authConfigFile, err := getAuthConfigFile()
if err != nil {
return err.Trace()
}
@ -68,18 +110,21 @@ func SaveConfig(a *Config) *probe.Error {
if err != nil {
return err.Trace()
}
if err := qc.Save(authConfigPath); err != nil {
if err := qc.Save(authConfigFile); err != nil {
return err.Trace()
}
return nil
}
// LoadConfig load donut config
// LoadConfig load auth config
func LoadConfig() (*Config, *probe.Error) {
authConfigPath, err := getAuthConfigPath()
authConfigFile, err := getAuthConfigFile()
if err != nil {
return nil, err.Trace()
}
if _, err := os.Stat(authConfigFile); err != nil {
return nil, probe.NewError(err)
}
a := &Config{}
a.Version = "0.0.1"
a.Users = make(map[string]*User)
@ -87,7 +132,7 @@ func LoadConfig() (*Config, *probe.Error) {
if err != nil {
return nil, err.Trace()
}
if err := qc.Load(authConfigPath); err != nil {
if err := qc.Load(authConfigFile); err != nil {
return nil, err.Trace()
}
return qc.Data().(*Config), nil

View File

@ -17,7 +17,10 @@
package rpc
import (
"errors"
"net/http"
"os"
"strings"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/probe"
@ -26,29 +29,126 @@ import (
// AuthService auth service
type AuthService struct{}
// AuthReply reply with new access keys and secret ids
type AuthReply struct {
AccessKeyID string `json:"accesskey"`
SecretAccessKey string `json:"secretaccesskey"`
// AuthArgs auth params
type AuthArgs struct {
User string `json:"user"`
}
func getAuth(reply *AuthReply) *probe.Error {
accessID, err := auth.GenerateAccessKeyID()
// AuthReply reply with new access keys and secret ids
type AuthReply struct {
Name string `json:"name"`
AccessKeyID string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
// generateAuth generate new auth keys for a user
func generateAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
config, err := auth.LoadConfig()
if err != nil {
if os.IsNotExist(err.ToGoError()) {
// Initialize new config, since config file doesn't exist yet
config = &auth.Config{}
config.Version = "0.0.1"
config.Users = make(map[string]*auth.User)
} else {
return err.Trace()
}
}
if _, ok := config.Users[args.User]; ok {
return probe.NewError(errors.New("Credentials already set, if you wish to change this invoke Reset() method"))
}
accessKeyID, err := auth.GenerateAccessKeyID()
if err != nil {
return err.Trace()
}
reply.AccessKeyID = string(accessID)
secretID, err := auth.GenerateSecretAccessKey()
reply.AccessKeyID = string(accessKeyID)
secretAccessKey, err := auth.GenerateSecretAccessKey()
if err != nil {
return err.Trace()
}
reply.SecretAccessKey = string(secretID)
reply.SecretAccessKey = string(secretAccessKey)
config.Users[args.User] = &auth.User{
Name: args.User,
AccessKeyID: string(accessKeyID),
SecretAccessKey: string(secretAccessKey),
}
if err := auth.SaveConfig(config); err != nil {
return err.Trace()
}
return nil
}
// Get auth keys
func (s *AuthService) Get(r *http.Request, args *Args, reply *AuthReply) error {
if err := getAuth(reply); err != nil {
// fetchAuth fetch auth keys for a user
func fetchAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
config, err := auth.LoadConfig()
if err != nil {
return err.Trace()
}
if _, ok := config.Users[args.User]; !ok {
return probe.NewError(errors.New("User not found"))
}
reply.AccessKeyID = config.Users[args.User].AccessKeyID
reply.SecretAccessKey = config.Users[args.User].SecretAccessKey
return nil
}
// resetAuth reset auth keys for a user
func resetAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
config, err := auth.LoadConfig()
if err != nil {
return err.Trace()
}
if _, ok := config.Users[args.User]; !ok {
return probe.NewError(errors.New("User not found"))
}
accessKeyID, err := auth.GenerateAccessKeyID()
if err != nil {
return err.Trace()
}
reply.AccessKeyID = string(accessKeyID)
secretAccessKey, err := auth.GenerateSecretAccessKey()
if err != nil {
return err.Trace()
}
reply.SecretAccessKey = string(secretAccessKey)
config.Users[args.User] = &auth.User{
Name: args.User,
AccessKeyID: string(accessKeyID),
SecretAccessKey: string(secretAccessKey),
}
return auth.SaveConfig(config).Trace()
}
// Generate auth keys
func (s *AuthService) Generate(r *http.Request, args *AuthArgs, reply *AuthReply) error {
if strings.TrimSpace(args.User) == "" {
return errors.New("Invalid argument")
}
if err := generateAuth(args, reply); err != nil {
return probe.WrapError(err)
}
return nil
}
// Fetch auth keys
func (s *AuthService) Fetch(r *http.Request, args *AuthArgs, reply *AuthReply) error {
if strings.TrimSpace(args.User) == "" {
return errors.New("Invalid argument")
}
if err := fetchAuth(args, reply); err != nil {
return probe.WrapError(err)
}
return nil
}
// Reset auth keys, generates new set of auth keys
func (s *AuthService) Reset(r *http.Request, args *AuthArgs, reply *AuthReply) error {
if strings.TrimSpace(args.User) == "" {
return errors.New("Invalid argument")
}
if err := resetAuth(args, reply); err != nil {
return probe.WrapError(err)
}
return nil

View File

@ -18,7 +18,8 @@ package rpc
import (
"net/http"
"time"
"github.com/minio/minio/pkg/version"
)
// Args basic json RPC params
@ -29,7 +30,7 @@ type Args struct {
// VersionReply version reply
type VersionReply struct {
Version string `json:"version"`
BuildDate string `json:"build-date"`
BuildDate string `json:"buildDate"`
}
// VersionService -
@ -38,8 +39,9 @@ type VersionService struct{}
func getVersion() string {
return "0.0.1"
}
func getBuildDate() string {
return time.Now().UTC().Format(http.TimeFormat)
return version.Version
}
func setVersionReply(reply *VersionReply) {

View File

@ -17,11 +17,14 @@
package controller
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
jsonrpc "github.com/gorilla/rpc/v2/json"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/controller/rpc"
. "gopkg.in/check.v1"
)
@ -36,6 +39,10 @@ var _ = Suite(&MySuite{})
var testRPCServer *httptest.Server
func (s *MySuite) SetUpSuite(c *C) {
root, err := ioutil.TempDir(os.TempDir(), "api-")
c.Assert(err, IsNil)
auth.SetAuthConfigPath(root)
testRPCServer = httptest.NewServer(getRPCHandler())
}
@ -81,8 +88,8 @@ func (s *MySuite) TestSysInfo(c *C) {
func (s *MySuite) TestAuth(c *C) {
op := rpc.Operation{
Method: "Auth.Get",
Request: rpc.Args{Request: ""},
Method: "Auth.Generate",
Request: rpc.AuthArgs{User: "newuser"},
}
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
c.Assert(err, IsNil)
@ -97,4 +104,40 @@ func (s *MySuite) TestAuth(c *C) {
c.Assert(reply, Not(DeepEquals), rpc.AuthReply{})
c.Assert(len(reply.AccessKeyID), Equals, 20)
c.Assert(len(reply.SecretAccessKey), Equals, 40)
op = rpc.Operation{
Method: "Auth.Fetch",
Request: rpc.AuthArgs{User: "newuser"},
}
req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
c.Assert(err, IsNil)
c.Assert(req.Get("Content-Type"), Equals, "application/json")
resp, err = req.Do()
c.Assert(err, IsNil)
c.Assert(resp.StatusCode, Equals, http.StatusOK)
var newReply rpc.AuthReply
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &newReply), IsNil)
resp.Body.Close()
c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{})
c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID)
c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey)
op = rpc.Operation{
Method: "Auth.Reset",
Request: rpc.AuthArgs{User: "newuser"},
}
req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
c.Assert(err, IsNil)
c.Assert(req.Get("Content-Type"), Equals, "application/json")
resp, err = req.Do()
c.Assert(err, IsNil)
c.Assert(resp.StatusCode, Equals, http.StatusOK)
var resetReply rpc.AuthReply
c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &resetReply), IsNil)
resp.Body.Close()
c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{})
c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID)
c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey)
}

View File

@ -107,6 +107,9 @@ func (e *Error) Trace(fields ...string) *Error {
// Internal trace - records the point at which it is invoked.
func (e *Error) trace(fields ...string) *Error {
if e == nil {
return nil
}
pc, file, line, _ := runtime.Caller(2)
function := runtime.FuncForPC(pc).Name()
_, function = filepath.Split(function)
@ -139,6 +142,9 @@ func (e *Error) Untrace() *Error {
// ToGoError returns original error message.
func (e *Error) ToGoError() error {
if e == nil || e.Cause == nil {
return nil
}
return e.Cause
}

View File

@ -75,7 +75,7 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) {
s.accessKeyID = string(accessKeyID)
s.secretAccessKey = string(secretAccessKey)
auth.SetAuthConfigPath(filepath.Join(root, "users.json"))
auth.SetAuthConfigPath(root)
perr = auth.SaveConfig(authConf)
c.Assert(perr, IsNil)