lijian 6 rokov pred
rodič
commit
ef682b2827

+ 2 - 0
install.sh

@@ -0,0 +1,2 @@
+cd services
+go install -v ./...

+ 6 - 6
pkg/models/user.go

@@ -14,7 +14,7 @@ type User struct {
 	Phone      string `sql:"type:varchar(20);not null;"`
 	Email      string `sql:"type:varchar(200);not null;"`
 	UserType   int    `sql:"default:1;not null;"`
-	VendorID   int
+	VendorID   uint
 	Status     int `sql:"default:1;not null;"`
 	Vendor     Vendor
 }
@@ -27,9 +27,9 @@ type LoginRequest struct {
 
 // Reqrequest 注册请求
 type Reqrequest struct {
-	UserName   string `json:"username"`
-	PassWord   string `json:"password"`
-	Phone      string `json:"phone"`
-	Email      string `json:"email"`
-	VendorName string `json:"company"`
+	UserName   string `json:"username" binding:"required"`
+	PassWord   string `json:"password" binding:"required"`
+	Phone      string `json:"phone" binding:"required"`
+	Email      string `json:"email" binding:"required"`
+	VendorName string `json:"company" binding:"required"`
 }

+ 8 - 0
pkg/rpcs/user.go

@@ -0,0 +1,8 @@
+package rpcs
+
+// ArgsUserModifyPass 用户修改密码参数
+type ArgsUserModifyPass struct {
+	UserID  uint   //用户ID
+	NewPass string // 新改密码
+	OldPass string //旧密码
+}

+ 18 - 0
run.sh

@@ -0,0 +1,18 @@
+export GOPATH=/Users/terrence/go
+
+sudo killall -9 httpaccess registry apiprovider devicemanager controller mqttaccess
+
+# start services
+#$GOPATH/bin/httpaccess -etcd http://localhost:2379 -httphost internal:443 -loglevel debug -usehttps -keyfile $GOPATH/src/github.com/PandoCloud/pando-cloud/pkg/server/testdata/key.pem -cafile $GOPATH/src/github.com/PandoCloud/pando-cloud/pkg/server/testdata/cert.pem &
+$GOPATH/bin/httpaccess -etcd http://192.168.175.60:2379 -httphost localhost:8088 -loglevel debug &
+$GOPATH/bin/registry -etcd http://192.168.175.60:2379 -rpchost localhost:20034 -aeskey ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP -dbhost 192.168.175.60 -dbname SparrowCloud -dbport 3306 -dbuser SparrowCloud -dbpass 123456 -loglevel debug &
+$GOPATH/bin/apiprovider -etcd http://192.168.175.60:2379 -loglevel debug  -httphost localhost:8888 -dbhost 192.168.175.60 -dbname SparrowCloud -dbport 3306 -dbuser SparrowCloud -dbpass 123456 &
+$GOPATH/bin/devicemanager -etcd http://192.168.175.60:2379 -loglevel debug  -rpchost localhost:20033 &
+$GOPATH/bin/controller -etcd http://192.168.175.60:2379 -loglevel debug  -rpchost localhost:20032 &
+#$GOPATH/bin/mqttaccess -etcd http://localhost:2379 -loglevel debug  -rpchost localhost:20030 -tcphost internal:1883 -usetls -keyfile $GOPATH/src/github.com/PandoCloud/pando-cloud/pkg/server/testdata/key.pem -cafile $GOPATH/src/github.com/PandoCloud/pando-cloud/pkg/server/testdata/cert.pem &
+$GOPATH/bin/mqttaccess -etcd http://192.168.175.60:2379 -loglevel debug  -rpchost localhost:20030 -tcphost 0.0.0.0:1883  &
+
+exit 0
+
+
+# etcd --listen-peer-urls="http://0.0.0.0:2380" --listen-client-urls="http://0.0.0.0:2379" -advertise-client-urls="http://0.0.0.0:2379" &

+ 8 - 0
services/apiprovider/actions.go

@@ -38,6 +38,14 @@ func renderError(code int, err error) Common {
 	return result
 }
 
+func done(result interface{}) Common {
+	return Common{
+		Code:    ErrOK,
+		Message: "success",
+		Result:  result,
+	}
+}
+
 func GetDeviceInfoByKey(params martini.Params, req *http.Request, r render.Render) {
 	key := req.URL.Query().Get("device_key")
 	server.Log.Printf("ACTION GetDeviceInfoByKey, key:: %v", key)

+ 0 - 40
services/apiprovider/actions_test.go

@@ -1,40 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"sparrow/pkg/models"
-	"sparrow/pkg/utils"
-	"testing"
-
-	"github.com/go-martini/martini"
-	"github.com/martini-contrib/render"
-)
-
-func startServer() {
-	martini.Env = martini.Prod
-	handler := martini.Classic()
-	handler.Use(render.Renderer())
-	route(handler)
-
-	handler.Run()
-}
-func struct2string(tar interface{}) string {
-	bytes, err := json.Marshal(tar)
-	if err != nil {
-		return ""
-	}
-	return string(bytes)
-}
-func TestUserLogin(t *testing.T) {
-	go startServer()
-	req := models.LoginRequest{
-		UserName: "lijian",
-		Password: "lijian",
-	}
-
-	body, err := utils.SendHttpRequest("http://localhost:3000/api/v1/login", struct2string(req), "POST", nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Fatal(string(body))
-}

+ 16 - 0
services/apiprovider/flags.go

@@ -7,8 +7,24 @@ import (
 const (
 	flagRabbitHost    = "rabbithost"
 	defaultRabbitHost = "amqp://pandocloud:123@192.168.175.60:5672/"
+
+	flagDBHost = "dbhost"
+	flagDBPort = "dbport"
+	flagDBName = "dbname"
+	flagDBUser = "dbuser"
+	flagDBPass = "dbpass"
+
+	defaultDBHost = "192.168.175.60"
+	defaultDBPort = "3306"
+	defaultDBName = "SparrowCloud"
+	defaultDBUser = "SparrowCloud"
 )
 
 var (
+	confDBHost     = flag.String(flagDBHost, defaultDBHost, "database host address.")
+	confDBPort     = flag.String(flagDBPort, defaultDBPort, "database host port.")
+	confDBName     = flag.String(flagDBName, defaultDBName, "database name.")
+	confDBUser     = flag.String(flagDBUser, defaultDBUser, "database user.")
+	confDBPass     = flag.String(flagDBPass, "", "databse password.")
 	confRabbitHost = flag.String(flagRabbitHost, defaultRabbitHost, "rabbitmq host address, amqp://user:password@ip:port/")
 )

+ 214 - 0
services/apiprovider/jwt.go

@@ -0,0 +1,214 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/go-martini/martini"
+
+	jwt "github.com/dgrijalva/jwt-go"
+)
+
+const (
+	//DefaultContextKey jwt
+	DefaultContextKey = "jwt"
+)
+
+// errorHandler error callback
+type errorHandler func(http.ResponseWriter, string)
+
+// TokenExtractor TokenExtractor
+type TokenExtractor func(*http.Request) (string, error)
+
+// Config config
+type Config struct {
+	ValidationKeyGetter jwt.Keyfunc
+
+	ContextKey string
+
+	ErrorHandler errorHandler
+
+	CredentialsOptional bool
+
+	Extractor TokenExtractor
+	// Default: true
+	Debug bool
+
+	// Default: false
+	EnableAuthOnOptions bool
+
+	// Default: nil
+	SigningMethod jwt.SigningMethod
+
+	// Default: false
+	Expiration bool
+}
+
+// Middleware the middleware for JSON Web tokens authentication method
+type Middleware struct {
+	Config Config
+}
+
+// OnError default error handler
+func OnError(res http.ResponseWriter, err string) {
+	http.Error(res, err, http.StatusUnauthorized)
+}
+
+// New constructs a new Secure instance with supplied options.
+func New(cfg ...Config) *Middleware {
+
+	var c Config
+	if len(cfg) == 0 {
+		c = Config{}
+	} else {
+		c = cfg[0]
+	}
+
+	if c.ContextKey == "" {
+		c.ContextKey = DefaultContextKey
+	}
+
+	if c.ErrorHandler == nil {
+		c.ErrorHandler = OnError
+	}
+
+	if c.Extractor == nil {
+		c.Extractor = FromAuthHeader
+	}
+
+	return &Middleware{Config: c}
+}
+
+func (m *Middleware) logf(format string, args ...interface{}) {
+	if m.Config.Debug {
+		log.Printf(format, args...)
+	}
+}
+
+// Get returns the user (&token) information for this client/request
+// func (m *Middleware) Get(req *http.Request) *jwt.Token {
+// 	return req.Header.Get(m.Config.ContextKey).(*jwt.Token)
+// }
+
+// Serve the middleware's action
+func (m *Middleware) Serve(ctx martini.Context, res http.ResponseWriter, req *http.Request) {
+	if err := m.CheckJWT(req, res, ctx); err != nil {
+		return
+	}
+	// If everything ok then call next.
+	ctx.Next()
+}
+
+// FromAuthHeader is a "TokenExtractor" that takes a give context and extracts
+// the JWT token from the Authorization header.
+func FromAuthHeader(req *http.Request) (string, error) {
+	authHeader := req.Header.Get("Authorization")
+	if authHeader == "" {
+		return "", nil // No error, just no token
+	}
+
+	// TODO: Make this a bit more robust, parsing-wise
+	authHeaderParts := strings.Split(authHeader, " ")
+	if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
+		return "", fmt.Errorf("Authorization header format must be Bearer {token}")
+	}
+
+	return authHeaderParts[1], nil
+}
+
+// CheckJWT the main functionality, checks for token
+func (m *Middleware) CheckJWT(req *http.Request, res http.ResponseWriter, ctx martini.Context) error {
+	if !m.Config.EnableAuthOnOptions {
+		if req.Method == http.MethodOptions {
+			return nil
+		}
+	}
+
+	// Use the specified token extractor to extract a token from the request
+	token, err := m.Config.Extractor(req)
+	// If debugging is turned on, log the outcome
+	if err != nil {
+		m.logf("Error extracting JWT: %v", err)
+	} else {
+		m.logf("Token extracted: %s", token)
+	}
+
+	// If an error occurs, call the error handler and return an error
+	if err != nil {
+		m.Config.ErrorHandler(res, err.Error())
+		return fmt.Errorf("Error extracting token: %v", err)
+	}
+
+	// If the token is empty...
+	if token == "" {
+		// Check if it was required
+		if m.Config.CredentialsOptional {
+			m.logf("  No credentials found (CredentialsOptional=true)")
+			// No error, just no token (and that is ok given that CredentialsOptional is true)
+			return nil
+		}
+
+		// If we get here, the required token is missing
+		errorMsg := "Required authorization token not found"
+		m.Config.ErrorHandler(res, errorMsg)
+		m.logf("  Error: No credentials found (CredentialsOptional=false)")
+		return fmt.Errorf(errorMsg)
+	}
+
+	// Now parse the token
+
+	parsedToken, err := jwt.Parse(token, m.Config.ValidationKeyGetter)
+	// Check if there was an error in parsing...
+	if err != nil {
+		m.logf("Error parsing token: %v", err)
+		m.Config.ErrorHandler(res, err.Error())
+		return fmt.Errorf("Error parsing token: %v", err)
+	}
+
+	if m.Config.SigningMethod != nil && m.Config.SigningMethod.Alg() != parsedToken.Header["alg"] {
+		message := fmt.Sprintf("Expected %s signing method but token specified %s",
+			m.Config.SigningMethod.Alg(),
+			parsedToken.Header["alg"])
+		m.logf("Error validating token algorithm: %s", message)
+		m.Config.ErrorHandler(res, errors.New(message).Error())
+		return fmt.Errorf("Error validating token algorithm: %s", message)
+	}
+
+	// Check if the parsed token is valid...
+	if !parsedToken.Valid {
+		m.logf("Token is invalid")
+		m.Config.ErrorHandler(res, "The token isn't valid")
+		return fmt.Errorf("Token is invalid")
+	}
+
+	if m.Config.Expiration {
+		if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
+			if expired := claims.VerifyExpiresAt(time.Now().Unix(), true); !expired {
+				return fmt.Errorf("Token is expired")
+			}
+		}
+	}
+
+	m.logf("JWT: %v", parsedToken)
+
+	// If we get here, everything worked and we can set the
+	// user property in context.
+	ctx.Map(parseToken(parsedToken))
+	return nil
+}
+
+// 把token解析成一个usertoken对象
+func parseToken(token *jwt.Token) *UserToken {
+	claims := token.Claims.(jwt.MapClaims)
+	ut := &UserToken{
+		UserID:   uint(claims["UserID"].(float64)),
+		UserName: claims["UserName"].(string),
+		UserType: claims["UserType"].(string),
+		RoleCode: int(claims["RoleCode"].(float64)),
+	}
+	return ut
+}

+ 27 - 2
services/apiprovider/main.go

@@ -1,10 +1,13 @@
 package main
 
 import (
+	"net/http"
 	"sparrow/pkg/server"
 
 	"github.com/go-martini/martini"
 	"github.com/martini-contrib/render"
+	"github.com/martini-contrib/sessions"
+	"github.com/xyproto/permissionsql"
 )
 
 func main() {
@@ -14,12 +17,34 @@ func main() {
 		server.Log.Fatal(err)
 		return
 	}
-
+	DSN := *confDBUser + ":" + *confDBPass + "@tcp(" + *confDBHost + ":" + *confDBPort + ")/" + *confDBName + "?charset=utf8&parseTime=True"
 	// martini setup
+	store := sessions.NewCookieStore([]byte("secret123"))
+	store.Options(sessions.Options{
+		MaxAge: 0,
+	})
+
 	martini.Env = martini.Prod
 	handler := martini.Classic()
+	perm, err := permissionsql.NewWithDSN(DSN, *confDBName)
+	if err != nil {
+		server.Log.Fatal(err)
+		return
+	}
 	handler.Use(render.Renderer())
-	route(handler)
+	permissionHandler := func(w http.ResponseWriter, req *http.Request, c martini.Context) {
+		// Check if the user has the right admin/user rights
+		if perm.Rejected(w, req) {
+			// Deny the request
+			http.Error(w, "Permission denied!", http.StatusForbidden)
+			// Reject the request by not calling the next handler below
+			return
+		}
+		// Call the next middleware handler
+		c.Next()
+	}
+	handler.Use(permissionHandler)
+	route(handler, perm)
 
 	// register a http handler
 	err = server.RegisterHTTPHandler(handler)

+ 3 - 2
services/apiprovider/response.go

@@ -2,8 +2,9 @@ package main
 
 // Common response fields
 type Common struct {
-	Code    int    `json:"code"`
-	Message string `json:"message"`
+	Code    int         `json:"code"`
+	Message string      `json:"message"`
+	Result  interface{} `json:"result"`
 }
 
 type DeviceInfoData struct {

+ 23 - 3
services/apiprovider/router.go

@@ -3,12 +3,22 @@ package main
 import (
 	"sparrow/pkg/models"
 
+	jwt "github.com/dgrijalva/jwt-go"
 	"github.com/go-martini/martini"
 	"github.com/martini-contrib/binding"
+	"github.com/xyproto/permissionsql"
 )
 
 // martini router
-func route(m *martini.ClassicMartini) {
+func route(m *martini.ClassicMartini, perm *permissionsql.Permissions) {
+
+	handler := New(Config{
+		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
+			return []byte(SignedString), nil
+		},
+		SigningMethod: jwt.SigningMethodHS256,
+	})
+
 	// find a device by key
 	m.Get("/application/v1/device/info", GetDeviceInfoByKey)
 
@@ -39,8 +49,18 @@ func route(m *martini.ClassicMartini) {
 	m.Post("/application/v1/devices/:identifier/rules",
 		ApplicationAuthOnDeviceIdentifer, CheckDeviceIdentifier,
 		AddRule)
+	// user login
+	m.Post("/api/v1/login", binding.Bind(models.LoginRequest{}),
+		UserLogin)
+	// user register
+	m.Post("/api/v1/reg", binding.Bind(models.Reqrequest{}),
+		UserRegister)
 
-	m.Post("/api/v1/login", binding.Bind(models.LoginRequest{}), UserLogin)
-	m.Post("/api/v1/reg", binding.Bind(models.Request))
+	// user api group
+	// jwt check and pass UserToken
+	m.Group("/api/v1/user", func(r martini.Router) {
+		// 修改密码
+		r.Post("/modifypass", ModifyPassword)
+	}, handler.Serve)
 
 }

+ 90 - 2
services/apiprovider/user.go

@@ -1,15 +1,103 @@
 package main
 
 import (
+	"errors"
 	"net/http"
 	"sparrow/pkg/models"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"time"
 
+	jwt "github.com/dgrijalva/jwt-go"
 	"github.com/martini-contrib/render"
 )
 
+// SignedString 签名
+const SignedString = "www.yehaoji.com"
+
+var (
+	// ErrBadRequestString 参数不全错误
+	ErrBadRequestString = errors.New("请求参数不全")
+)
+
+// UserToken 用户token结构体
+type UserToken struct {
+	UserID   uint
+	UserName string
+	UserType string
+	RoleCode int
+	jwt.StandardClaims
+}
+
 // UserLogin 用户登陆
 func UserLogin(loginRequest models.LoginRequest, r render.Render) {
-	err :=
-		r.JSON(http.StatusOK, loginRequest)
+
+	claims := UserToken{
+		UserID:   1,
+		UserName: "test",
+		UserType: "member",
+		RoleCode: 100,
+	}
+	claims.ExpiresAt = time.Now().Add(time.Hour * 24).Unix()
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	ser, _ := token.SignedString([]byte(SignedString))
+	r.JSON(http.StatusOK, ser)
+	return
+}
+
+// UserRegister 用户注册
+func UserRegister(req models.Reqrequest, r render.Render) {
+	vargs := models.Vendor{
+		VendorName: req.VendorName,
+	}
+	vreply := &models.Vendor{}
+
+	err := server.RPCCallByName("registry", "Registry.SaveVendor", &vargs, vreply)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	uargs := &models.User{
+		UserName: req.UserName,
+		UserPass: req.PassWord,
+		VendorID: vreply.ID,
+		Phone:    req.Phone,
+		Email:    req.Email,
+	}
+	ureply := &models.User{}
+	err = server.RPCCallByName("registry", "Registry.Register", &uargs, ureply)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	res := map[string]interface{}{
+		"username": uargs.UserName,
+		"phone":    uargs.Phone,
+		"email":    uargs.Email,
+	}
+	r.JSON(http.StatusOK, res)
+	return
+}
+
+// ModifyPassword 用户修改密码
+func ModifyPassword(req *http.Request, r render.Render, user *UserToken) {
+	newpass := req.URL.Query().Get("newpass")
+	oldpass := req.URL.Query().Get("oldpass")
+	if len(newpass) == 0 || len(oldpass) == 0 {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, ErrBadRequestString))
+		return
+	}
+	args := rpcs.ArgsUserModifyPass{
+		UserID:  user.UserID,
+		NewPass: newpass,
+		OldPass: oldpass,
+	}
+	err := server.RPCCallByName("registry", "Registry.ModifyPass", &args, nil)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	r.JSON(http.StatusOK, done("修改成功"))
 	return
 }

+ 6 - 4
services/httpaccess/actions.go

@@ -3,13 +3,14 @@ package main
 import (
 	"encoding/hex"
 	"errors"
+	"math/rand"
+	"net/http"
 	"sparrow/pkg/models"
 	"sparrow/pkg/rpcs"
 	"sparrow/pkg/server"
 	"sparrow/pkg/token"
+
 	"github.com/martini-contrib/render"
-	"math/rand"
-	"net/http"
 )
 
 const (
@@ -59,7 +60,7 @@ func RegisterDevice(args DeviceRegisterArgs, r render.Render) {
 
 	result := DeviceRegisterResponse{}
 	result.Data = DeviceRegisterData{
-		DeviceId:         device.ID,
+		DeviceId:         int64(device.ID),
 		DeviceSecret:     device.DeviceSecret,
 		DeviceKey:        device.DeviceKey,
 		DeviceIdentifier: device.DeviceIdentifier,
@@ -68,6 +69,7 @@ func RegisterDevice(args DeviceRegisterArgs, r render.Render) {
 	return
 }
 
+// AuthDevice device auth
 func AuthDevice(args DeviceAuthArgs, r render.Render) {
 	server.Log.Printf("ACTION AuthDevice, args:: %v", args)
 	device := &models.Device{}
@@ -84,7 +86,7 @@ func AuthDevice(args DeviceAuthArgs, r render.Render) {
 	}
 
 	hepler := token.NewHelper(*confRedisHost)
-	token, err := hepler.GenerateToken(uint64(device.ID))
+	token, err := hepler.GenerateToken(uint64(int64(device.ID)))
 	if err != nil {
 		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
 		return

+ 2 - 1
services/registry/registry_test.go

@@ -213,8 +213,9 @@ func testDevice(t *testing.T, r *Registry) {
 func testModifyPassword(t *testing.T, r *Registry) {
 	userid := uint(1)
 	pass := "lijian"
+	oldpass := "1234356"
 	reply := &models.User{}
-	err := r.ModifyPass(userid, pass, reply)
+	err := r.ModifyPass(userid, oldpass, pass, reply)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 15 - 6
services/registry/user.go

@@ -3,6 +3,7 @@ package main
 import (
 	"errors"
 	"sparrow/pkg/models"
+	"sparrow/pkg/rpcs"
 )
 
 // ErrUserNameExists 用户名已经存在
@@ -54,20 +55,28 @@ func (r *Registry) Register(args *models.User, reply *models.User) error {
 	if err != nil {
 		return err
 	}
-
+	reply = args
 	return nil
 }
 
 //ModifyPass 修改密码
-func (r *Registry) ModifyPass(userid uint, pass string, reply *models.User) error {
+func (r *Registry) ModifyPass(args *rpcs.ArgsUserModifyPass, reply *models.User) error {
 	db, err := getDB()
 	if err != nil {
 		return err
 	}
-	pass = mdd(pass)
-	err = db.Model(&reply).Where(userid).Update("user_pass", pass).Error
+	pass := mdd(args.NewPass)
+	oldpass := mdd(args.OldPass)
+	var count int
+	err = db.Model(&reply).Where(map[string]interface{}{
+		"id":        args.UserID,
+		"user_pass": oldpass,
+	}).Update("user_pass", pass).Count(&count).Error
 	if err != nil {
-		return err
+		return ErrNameOrPassError
+	}
+	if count == 0 {
+		return ErrNameOrPassError
 	}
 	return nil
 }
@@ -81,7 +90,7 @@ func checkUserExists(name, phone, email string) error {
 		return err
 	}
 	reply := &models.User{}
-	db.Find(reply, map[string]interface{}{"user_name": name})
+	db.First(reply, map[string]interface{}{"user_name": name})
 	if reply.ID != 0 {
 		return ErrUserNameExists
 	}