Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/refactor persistence #15

Merged
merged 9 commits into from Apr 1, 2021
16 changes: 12 additions & 4 deletions cmd/cassemctl/commands_resctl_user.go
Expand Up @@ -5,6 +5,10 @@ import (

"github.com/yeqown/cassem/internal/authorizer"

"github.com/yeqown/cassem/pkg/hash"

"github.com/yeqown/cassem/internal/persistence"

"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -52,7 +56,7 @@ func addUserCommand() *cli.Command {
Category: "user",
Flags: []cli.Flag{_userMetaDataFlag},
Action: func(ctx *cli.Context) error {
auth, err := getAuthorizer(ctx)
repo, err := getRepository(ctx)
if err != nil {
return err
}
Expand All @@ -62,7 +66,11 @@ func addUserCommand() *cli.Command {
return err
}

_, err = auth.AddUser(md.Account, md.Password, md.Name)
err = repo.CreateUser(&persistence.User{
Account: md.Account,
PasswordWithSalt: hash.WithSalt(md.Password, "cassem"),
Name: md.Name,
})
return err
},
}
Expand All @@ -75,7 +83,7 @@ func resetUserPasswordCommand() *cli.Command {
Category: "user",
Flags: []cli.Flag{_userMetaDataFlag},
Action: func(ctx *cli.Context) error {
auth, err := getAuthorizer(ctx)
repo, err := getRepository(ctx)
if err != nil {
return err
}
Expand All @@ -85,7 +93,7 @@ func resetUserPasswordCommand() *cli.Command {
return err
}

return auth.ResetPassword(md.Account, md.Password)
return repo.ResetPassword(md.Account, hash.WithSalt(md.Password, "cassem"))
},
}
}
Expand Down
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
"github.com/pkg/errors"
"github.com/yeqown/log"
)
Expand All @@ -26,20 +25,40 @@ e = some(where (p.eft == allow))
m = r.sub == p.sub && obj_match(r.obj, p.obj) && act_match(r.act, p.act)
`

type IEnforcer interface {
Enforce(req *EnforceRequest) bool
ListSubjectPolicies(subject string) []Policy
UpdateSubjectPolicies(subject string, policies []Policy) error
}

type IAuthorizer interface {
IEnforcer

Migrate() error
}

type EnforceRequest struct {
Subject string
Object string
Action string

//Namespace string
//Container string
//Pair string
}

// casbinAuthorities implement IAuthorizer based on casbin.ACL model.
type casbinAuthorities struct {
aclEnforcer *casbin.Enforcer
userRepo persistence.UserRepository
repo persistence.Repository
}

func New(c *conf.MySQL) (auth IAuthorizer, err error) {
db, err := mysql.Connect(c)
repo, err := mysql.New(c)
if err != nil {
return nil, errors.Wrap(err, "authorizer.New could not connect to DB")
return nil, errors.Wrap(err, "authorizer.New could not load persistence")
}

// adapter
a, err := gormadapter.NewAdapterByDBUseTableName(db, "cassem", "permission_policy")
a, err := repo.PolicyAdapter()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -87,7 +106,7 @@ func New(c *conf.MySQL) (auth IAuthorizer, err error) {
})

auth = casbinAuthorities{
userRepo: mysql.NewUserRepository(db),
repo: repo,
aclEnforcer: e,
}

Expand All @@ -97,18 +116,14 @@ func New(c *conf.MySQL) (auth IAuthorizer, err error) {
// Migrate ...
// DONE(@yeqown): migrate to init data, only be called cassemctl.
func (c casbinAuthorities) Migrate() error {
if err := c.userRepo.Migrate(); err != nil {
return errors.Wrap(err, "failed to migrate user table")
}

// DONE(@yeqown) add root account automatically, and add all permissions to root account.
u, err := c.AddUser("admin", "cassem", "admin")
if err != nil {
u := &persistence.User{Account: "admin", PasswordWithSalt: "cassem", Name: "admin"}
if err := c.repo.CreateUser(u); err != nil {
return errors.Wrap(err, "failed to create root account")
}

token := NewToken(int(u.ID))
if err = c.UpdateSubjectPolicies(token.Subject(), allPolicies); err != nil {
if err := c.UpdateSubjectPolicies(token.Subject(), AllPolicies); err != nil {
return errors.Wrap(err, "failed to assign all policy to root account")
}

Expand Down Expand Up @@ -140,31 +155,6 @@ func (c casbinAuthorities) Enforce(req *EnforceRequest) bool {
return allow
}

func (c casbinAuthorities) UpdateSubjectPolicies(subject string, policies []Policy) error {
_, err := c.aclEnforcer.RemoveFilteredPolicy(0, subject)
if err != nil {
return err
}

in := make([][]string, 0, len(policies))
for _, policy := range policies {
if err = validPolicy(subject, policy); err != nil {
log.WithFields(log.Fields{
"subject": subject,
"policy": policy,
}).Warnf("policy invalid, skip")
}
in = append(in, []string{policy.Subject, policy.Object, policy.Action})
}

_, err = c.aclEnforcer.AddPolicies(in)
if err != nil {
return err
}

return nil
}

func (c casbinAuthorities) ListSubjectPolicies(subject string) []Policy {
out := c.aclEnforcer.GetFilteredPolicy(0, subject)

Expand All @@ -188,3 +178,28 @@ func (c casbinAuthorities) ListSubjectPolicies(subject string) []Policy {

return policies
}

func (c casbinAuthorities) UpdateSubjectPolicies(subject string, policies []Policy) error {
_, err := c.aclEnforcer.RemoveFilteredPolicy(0, subject)
if err != nil {
return err
}

in := make([][]string, 0, len(policies))
for _, policy := range policies {
if err = ValidPolicy(subject, policy); err != nil {
log.WithFields(log.Fields{
"subject": subject,
"policy": policy,
}).Warnf("policy invalid, skip")
}
in = append(in, []string{policy.Subject, policy.Object, policy.Action})
}

_, err = c.aclEnforcer.AddPolicies(in)
if err != nil {
return err
}

return nil
}
Expand Up @@ -86,19 +86,20 @@ func Test_IAuthorizer_AddPolicy(t *testing.T) {
assert.Equal(t, false, allow)
}

func Test_IAuthorizer_LoginAndSession(t *testing.T) {
a, err := authorizer.New(_conf)
require.Nil(t, err)

tokenString, err := a.Login("root", "123456")
require.Nil(t, err)
t.Log(tokenString)

token, err := a.Session(tokenString)
require.Nil(t, err)

assert.NotEmpty(t, token.UserId)
}
//
//func Test_IAuthorizer_LoginAndSession(t *testing.T) {
// a, err := authorizer.New(_conf)
// require.Nil(t, err)
//
// _, tokenString, err := a.Login("root", "123456")
// require.Nil(t, err)
// t.Log(tokenString)
//
// token, err := authorizer.Session(tokenString)
// require.Nil(t, err)
//
// assert.NotEmpty(t, token.UserId)
//}

func Test_IAuthorizer_ListPolicy(t *testing.T) {
a, err := authorizer.New(_conf)
Expand Down
Expand Up @@ -2,9 +2,9 @@ package authorizer

import (
"fmt"
"strconv"

"github.com/yeqown/cassem/internal/persistence"
"github.com/yeqown/cassem/pkg/hash"

"github.com/dgrijalva/jwt-go"
"github.com/pkg/errors"
Expand All @@ -15,32 +15,20 @@ var (
_SECRET_ = []byte("cassem")
)

func (c casbinAuthorities) AddUser(account, password, name string) (*persistence.UserDO, error) {
u := &persistence.UserDO{
Account: account,
PasswordWithSalt: hash.WithSalt(password, "cassem"),
Name: name,
}

return u, c.userRepo.Create(u)
// Token is the bridge between authorizer and HTTP API.
type Token struct {
UserId int
}

func (c casbinAuthorities) Login(account, password string) (*persistence.UserDO, string, error) {
u, err := c.userRepo.QueryUser(account)
if err != nil {
return nil, "", err
}

if u.PasswordWithSalt != hash.WithSalt(password, "cassem") {
return nil, "", errors.New("account and password could not match")
}
func NewToken(uid int) *Token {
return &Token{UserId: uid}
}

// DONE(@yeqown): generate jwt token
token, err := genToken(u)
return u, token, err
func (t Token) Subject() string {
return "uid:" + strconv.Itoa(t.UserId)
}

func (c casbinAuthorities) Session(tokenString string) (*Token, error) {
func Session(tokenString string) (*Token, error) {
uid, err := parseToken(tokenString)
if err != nil {
return nil, err
Expand All @@ -50,7 +38,7 @@ func (c casbinAuthorities) Session(tokenString string) (*Token, error) {
}

// DONE(@yeqown): extract secret from code to global.
func genToken(u *persistence.UserDO) (string, error) {
func GenToken(u *persistence.User) (string, error) {
atClaims := jwt.MapClaims{}
atClaims["authorized"] = true
atClaims["user_id"] = u.ID
Expand Down Expand Up @@ -90,15 +78,3 @@ func parseToken(tokenString string) (int, error) {

return int(uid), nil
}

func (c casbinAuthorities) ResetPassword(account, password string) error {
return c.userRepo.ResetPassword(account, hash.WithSalt(password, "cassem"))
}

func (c casbinAuthorities) PagingUsers(limit, offset int, accountPattern string) ([]*persistence.UserDO, int, error) {
return c.userRepo.PagingUsers(&persistence.PagingUsersFilter{
Limit: limit,
Offset: offset,
AccountPattern: accountPattern,
})
}
54 changes: 2 additions & 52 deletions internal/authorizer/authorizer.go → internal/authorizer/types.go
@@ -1,10 +1,6 @@
package authorizer

import (
"strconv"

"github.com/yeqown/cassem/internal/persistence"

"github.com/pkg/errors"
)

Expand All @@ -24,7 +20,7 @@ const (
)

var (
allPolicies = []Policy{
AllPolicies = []Policy{
{Subject: "", Object: OBJ_NAMESPACE, Action: ACTION_ANY},
{Subject: "", Object: OBJ_CONTAINER, Action: ACTION_ANY},
{Subject: "", Object: OBJ_PAIR, Action: ACTION_ANY},
Expand All @@ -39,31 +35,14 @@ var (
}
)

type EnforceRequest struct {
Subject string
Object string
Action string

//Namespace string
//Container string
//Pair string
}

// IAuthorizer
type IAuthorizer interface {
IAuthorizeManager

Enforce(req *EnforceRequest) bool
}

// Policy contains whole data what describes a ACL rule.
type Policy struct {
Subject string
Object string
Action string
}

func validPolicy(subject string, p Policy) (err error) {
func ValidPolicy(subject string, p Policy) (err error) {
var errmsg string
defer func() {
if errmsg != "" {
Expand All @@ -84,32 +63,3 @@ func validPolicy(subject string, p Policy) (err error) {

return
}

// Token is the bridge between authorizer and HTTP API.
type Token struct {
UserId int
}

func NewToken(uid int) *Token {
return &Token{UserId: uid}
}

func (t Token) Subject() string {
return "uid:" + strconv.Itoa(t.UserId)
}

// IAuthorizeManager manages user, roles.
type IAuthorizeManager interface {
Migrate() error

// user permissions manage API
ListSubjectPolicies(subject string) []Policy
UpdateSubjectPolicies(subject string, policies []Policy) error

// user and session manage API
AddUser(account, password, name string) (*persistence.UserDO, error)
Login(account, password string) (*persistence.UserDO, string, error)
Session(tokenString string) (*Token, error)
ResetPassword(account, password string) error
PagingUsers(limit, offset int, accountPattern string) ([]*persistence.UserDO, int, error)
}