Quellcode durchsuchen

实现简单的权限控制

Minho vor 7 Jahren
Ursprung
Commit
f7fc11ed7c

+ 31 - 0
acl/acl.go

@@ -0,0 +1,31 @@
+package acl
+
+import (
+	"sync"
+	"fmt"
+)
+
+var AclList = &sync.Map{}
+
+func AddMemberPermission(account string,resource Resource)  {
+	key := account + "!" + resource.Code
+
+	AclList.Store(key,resource)
+}
+
+//判断指定的资源是否可以访问
+func IsAllow(account ,controllerName,actionName,methodName string) bool {
+
+	key := fmt.Sprintf("%s!%s!%s!%s",account,controllerName,actionName,methodName)
+	fmt.Println(key)
+	if _,ok := AclList.Load(key);ok {
+		return true
+	}
+	key = fmt.Sprintf("%s!%s!%s!*",account,controllerName,actionName)
+	fmt.Println(key)
+	if _,ok := AclList.Load(key);ok {
+		return true
+	}
+
+	return false
+}

+ 111 - 0
acl/resource.go

@@ -0,0 +1,111 @@
+package acl
+
+var Modules = make(map[string]*Module)
+
+//模块
+type Module struct {
+	Name string
+	Description string
+	Code string
+	Resources map[string]*Resource
+}
+
+//资源
+type Resource struct {
+	Name string				`json:"name"`
+	Code string				`json:"code"`
+	ControllerName	string 	`json:"controller_name"`
+	ActionName string		`json:"action_name"`
+	MethodName string		`json:"method_name"`
+}
+
+
+func NewModule() *Module  {
+	return &Module{ Resources : make(map[string]*Resource)}
+}
+
+func init()  {
+
+	Modules["Common"] = &Module{
+		Name : "公共功能",
+		Code : "Common",
+		Description:"所有用户都有的功能",
+		Resources : map[string]*Resource {
+			"Common!Account!Login!*" : { Name: "用户登录" , Code:"Account!Login!*", ControllerName:"Account",ActionName:"Login",MethodName:"*"},
+			"Common!Account!Register!*" : { Name: "用户注册" , Code:"Account!Register!*", ControllerName:"Account",ActionName:"Register",MethodName:"*"},
+			"Common!Account!FindPassword!*" : { Name: "找回密码" , Code:"Account!FindPassword!*", ControllerName:"Account",ActionName:"FindPassword",MethodName:"*"},
+			"Common!Account!ValidEmail!*" : { Name: "邮箱修改密码" , Code:"Account!ValidEmail!*", ControllerName:"Account",ActionName:"ValidEmail",MethodName:"*"},
+			"Common!Account!Logout!*" : { Name: "退出登录" , Code:"Account!Logout!*", ControllerName:"Account",ActionName:"Logout",MethodName:"*"},
+			"Common!Account!Captcha!*" : { Name: "图片验证码" , Code:"Account!Captcha!*", ControllerName:"Account",ActionName:"Captcha",MethodName:"*"},
+			"Common!Home!Index!*" : { Name:"站点首页",Code:"Home!Index!*",ControllerName:"Home",ActionName:"Index",MethodName:"*"},
+			"Common!Search!Index!*" : { Name:"项目搜索",Code:"Search!Index!*",ControllerName:"Search",ActionName:"Index",MethodName:"*"},
+			"Common!Error!Error404!*" : { Name:"404页面", Code:"Error!Index!*", ControllerName:"Error", ActionName:"Error404",MethodName:"*" },
+			"Common!Error!Error403!*" : { Name:"403页面", Code:"Error!Index!*", ControllerName:"Error", ActionName:"Error403",MethodName:"*" },
+			"Common!Error!Error500!*" : { Name:"500页面", Code:"Error!Error500!*", ControllerName:"Error", ActionName:"Error500",MethodName:"*" },
+		},
+	}
+
+	Modules["MemberCommon"] = &Module{
+		Name : "用户公共功能",
+		Code : "MemberCommon",
+		Description:"只有登录用户才有的功能",
+		Resources : map[string]*Resource {
+			"MemberCommon!Book!Index!*" : { Name: "项目列表" , Code:"Book!Index!*", ControllerName:"Book",ActionName:"Index",MethodName:"*"},
+			"MemberCommon!Book!Dashboard!*" : { Name: "项目概述" , Code:"Book!Index!*", ControllerName:"Book",ActionName:"Dashboard",MethodName:"*"},
+		},
+	}
+
+	Modules["Book"] = &Module{
+		Name:"项目管理",
+		Code:"Book",
+		Resources: map[string]*Resource {
+			"Book!Book!Setting!*" : { Name: "项目设置查看" , Code:"Book!Setting!*", ControllerName:"Book",ActionName:"Setting",MethodName:"*"},
+			"Book!Book!SaveBook!*" : { Name: "项目设置保存" , Code:"Book!SaveBook!*", ControllerName:"Book",ActionName:"SaveBook",MethodName:"*"},
+		},
+	}
+
+	Modules["Document"] = &Module{
+		Name:"文档管理",
+		Code:"Book",
+		Resources: map[string]*Resource {
+
+		},
+	}
+
+	Modules["Label"] = &Module{
+		Name:"标签管理",
+		Code:"Book",
+		Resources: map[string]*Resource {
+
+		},
+	}
+	Modules["Manager"] = &Module{
+		Name:"后台管理",
+		Code:"Book",
+		Resources: map[string]*Resource {
+
+		},
+	}
+
+	for _,resource := range Modules["Common"].Resources {
+		AddMemberPermission("anonymous",*resource)
+	}
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 0 - 1
commands/command.go

@@ -97,7 +97,6 @@ func RegisterModel() {
 		new(models.Label),
 		new(models.MemberGroup),
 		new(models.MemberGroupMembers),
-		new(models.Resource),
 	)
 	//migrate.RegisterMigration()
 }

+ 57 - 6
commands/install.go

@@ -9,6 +9,7 @@ import (
 	"github.com/lifei6671/mindoc/conf"
 	"github.com/lifei6671/mindoc/models"
 	"github.com/lifei6671/mindoc/utils"
+	"github.com/astaxie/beego"
 )
 
 //系统安装.
@@ -79,24 +80,32 @@ func initialization() {
 	err := models.NewOption().Init()
 
 	if err != nil {
-		panic(err.Error())
+		beego.Error("初始化全局配置失败 => ",err.Error())
 		os.Exit(1)
 	}
 
+
+	InitMemberGroup()
+
+
 	member, err := models.NewMember().FindByFieldFirst("account", "admin")
+
+	//如果用户不存在,则添加用户,否则需要批量更新用户角色
 	if err == orm.ErrNoRows {
+		beego.Info("正在创建默认用户.")
 
 		member.Account = "admin"
 		member.Avatar = "/static/images/headimgurl.jpg"
 		member.Password = "123456"
 		member.AuthMethod = "local"
-		member.Role = 0
+		member.Role = 1
 		member.Email = "[email protected]"
 
 		if err := member.Add(); err != nil {
-			panic("Member.Add => " + err.Error())
+			beego.Error("创建默认用户失败 => " + err.Error())
 			os.Exit(0)
 		}
+		beego.Info("正在创建默认项目.")
 
 		book := models.NewBook()
 
@@ -116,18 +125,60 @@ func initialization() {
 		book.Theme = "default"
 
 		if err := book.Insert(); err != nil {
-			panic("Book.Insert => " + err.Error())
-			os.Exit(0)
+			beego.Error("创建默认项目失败 => " , err)
+			os.Exit(1)
 		}
+
 	}
+}
+
+//初始化用户组
+func InitMemberGroup()  {
+	beego.Info("正在创建用户组.")
 
 	group := models.NewMemberGroup()
+
 	group.GroupId = 1
 	group.GroupName = "管理员组"
-	group.GroupNumber = 1
+	group.GroupNumber = 0
+	group.CreateTime = time.Now()
+	group.CreateAt = 1
+	group.IsEnableDelete = false
+
+	group.InsertOrUpdate()
+
+	if err := group.InsertOrUpdate();err != nil{
+		beego.Error("创建用户组失败 ",group.GroupName,err)
+		os.Exit(1)
+	}
+
+
+	group.GroupId = 2
+	group.GroupName = "普通用户组"
+	group.GroupNumber = 0
 	group.CreateTime = time.Now()
 	group.CreateAt = 1
 	group.IsEnableDelete = false
 
+	group.InsertOrUpdate()
+
+	if err := group.InsertOrUpdate();err != nil{
+		beego.Error("创建用户组失败 ",group.GroupName,err)
+		os.Exit(1)
+	}
+
+
+	group.GroupId = 3
+	group.GroupName = "匿名用户组"
+	group.GroupNumber = 0
+	group.CreateTime = time.Now()
+	group.CreateAt = 1
+	group.IsEnableDelete = false
+	group.Resources = "1,2,3,4,5"
+
+	if err := group.InsertOrUpdate();err != nil{
+		beego.Error("创建用户组失败 ",group.GroupName,err)
+		os.Exit(1)
+	}
 
 }

+ 2 - 2
controllers/AccountController.go

@@ -337,7 +337,7 @@ func (c *AccountController) ValidEmail() {
 	password2 := c.GetString("password2")
 	captcha := c.GetString("code")
 	token := c.GetString("token")
-	mail := c.GetString("mail")
+	email := c.GetString("mail")
 
 	if password1 == "" {
 		c.JsonResult(6001, "密码不能为空")
@@ -368,7 +368,7 @@ func (c *AccountController) ValidEmail() {
 	}
 	sub_time := member_token.SendTime.Sub(time.Now())
 
-	if !strings.EqualFold(member_token.Email, mail) || sub_time.Minutes() > float64(mail_conf.MailExpired) || !member_token.ValidTime.IsZero() {
+	if !strings.EqualFold(member_token.Email, email) || sub_time.Minutes() > float64(mail_conf.MailExpired) || !member_token.ValidTime.IsZero() {
 
 		c.JsonResult(6008, "验证码已过期,请重新操作。")
 	}

+ 8 - 18
controllers/BaseController.go

@@ -12,12 +12,12 @@ import (
 	"github.com/lifei6671/mindoc/conf"
 	"github.com/lifei6671/mindoc/models"
 	"github.com/lifei6671/mindoc/utils"
+	"github.com/lifei6671/mindoc/acl"
 )
 
 type BaseController struct {
 	beego.Controller
 	Member                *models.Member
-	MemberResourceList	  []*models.Resource
 	Option                map[string]string
 	EnableAnonymous       bool
 	EnableDocumentHistory bool
@@ -78,25 +78,12 @@ func (c *BaseController) Prepare() {
 			}
 		}
 	}
-	roleId := 4
-	if c.Member != nil && c.Member.MemberId > 0 {
-		roleId = c.Member.Role
-	}
-
-	resourceList,err := models.NewMemberGroup().FindMemberGroupResourceList(roleId)
-	if err != nil {
-		beego.Error("获取用户许可资源时出错 =>", err)
-		c.ShowErrorPage(500,"获取用户许可资源时出错")
-	}
-	c.MemberResourceList = resourceList
-	c.Data["MemberResource"] = resourceList
+	//如果没有访问权限
+	if !(acl.IsAllow("anonymous",strings.TrimSuffix(controller,"Controller"),action,c.Ctx.Input.Method())  ||
+		(c.Member != nil && c.Member.Role > 0 && acl.IsAllow(c.Member.Account,strings.TrimSuffix(controller,"Controller"),action,c.Ctx.Input.Method()))) {
 
-	for _,resource := range resourceList {
-		if resource.ControllerName == controller && resource.ActionName == action && resource.HttpMethod == c.Ctx.Input.Method() {
-			return
-		}
+		c.ShowErrorPage(403,"权限不足")
 	}
-	c.ShowErrorPage(403,"权限不足")
 }
 
 // SetMember 获取或设置当前登录用户信息,如果 MemberId 小于 0 则标识删除 Session
@@ -109,6 +96,9 @@ func (c *BaseController) SetMember(member models.Member) {
 	} else {
 		c.SetSession(conf.LoginSessionName, member)
 		c.SetSession("uid", member.MemberId)
+		for _,resource := range acl.Modules["MemberCommon"].Resources {
+			acl.AddMemberPermission(member.Account,*resource)
+		}
 	}
 }
 

+ 4 - 1
controllers/BookController.go

@@ -92,6 +92,9 @@ func (c *BookController) Setting() {
 	c.Prepare()
 	c.TplName = "book/setting.tpl"
 
+	if c.Ctx.Input.IsPost() {
+		saveBook(c)
+	}
 	key := c.Ctx.Input.Param(":key")
 
 	if key == "" {
@@ -120,7 +123,7 @@ func (c *BookController) Setting() {
 }
 
 //保存项目信息
-func (c *BookController) SaveBook() {
+func saveBook(c *BookController) {
 	c.Prepare()
 	bookResult, err := c.IsPermission()
 

+ 1 - 0
main.go

@@ -12,6 +12,7 @@ import (
 	"github.com/lifei6671/mindoc/commands"
 	"github.com/lifei6671/mindoc/commands/daemon"
 	_ "github.com/lifei6671/mindoc/routers"
+	_ "github.com/lifei6671/mindoc/acl"
 	_ "github.com/mattn/go-sqlite3"
 )
 

+ 9 - 3
models/MemberGroupModel.go

@@ -21,7 +21,7 @@ type MemberGroup struct {
 	ModifyTime time.Time     	`orm:"column(modify_time);type(datetime);auto_now" json:"modify_time"`
 	Resources string		 	`orm:"column(resources);type(text);null" json:"-"`
 	IsEnableDelete bool			`orm:"column(is_enable_delete);type(bool);default(true)" json:"is_enable_delete"`
-	ResourceList []*Resource	`orm:"-" json:"resource_list"`
+	ResourceList []*ResourceModel	`orm:"-" json:"resource_list"`
 	ModifyAt   int           	`orm:"column(modify_at);type(int)" json:"-"`
 	ModifyName string 		 	`orm:"-" json:"modify_name"`
 	ModifyRealName string 	 	`orm:"-" json:"modify_real_name"`
@@ -164,7 +164,13 @@ func (m *MemberGroup) InsertOrUpdate(cols...string) error {
 	}
 
 	var err error
+	//如果用户组已存在
 	if m.GroupId > 0 {
+		group := &MemberGroup{}
+
+		err = o.QueryTable(m.TableNameWithPrefix()).Filter("group_id",m.GroupId).One(group)
+	}
+	if err == nil {
 		_,err = o.Update(m, cols...)
 	}else{
 		_,err = o.Insert(m)
@@ -209,7 +215,7 @@ func (m *MemberGroup) FindMemberGroupList(keyword string) ([]*MemberGroup,error)
 }
 
 //查询指定用户组的资源列表
-func (m *MemberGroup) FindMemberGroupResourceList(groupId int) ([]*Resource,error) {
+func (m *MemberGroup) FindMemberGroupResourceList(groupId int) ([]*ResourceModel,error) {
 	o := orm.NewOrm()
 
 	memberGroup := NewMemberGroup()
@@ -224,7 +230,7 @@ func (m *MemberGroup) FindMemberGroupResourceList(groupId int) ([]*Resource,erro
 	if memberGroup.Resources != "" {
 		resourceIds := strings.Split(strings.Trim(memberGroup.Resources,","),",")
 
-		var resources []*Resource
+		var resources []*ResourceModel
 		_,err = o.QueryTable(NewResource().TableNameWithPrefix()).Filter("resource_id__in",resourceIds).All(resources)
 		if err != nil {
 			beego.Error("查询用户组资源时出错 =>", err)

+ 25 - 13
models/ResourceModel.go

@@ -7,7 +7,7 @@ import (
 	"github.com/astaxie/beego"
 )
 
-type Resource struct {
+type ResourceModel struct {
 	//主键
 	ResourceId int		 		`orm:"column(resource_id);pk;auto;unique;" json:"resource_id"`
 	//分组ID
@@ -22,41 +22,53 @@ type Resource struct {
 }
 
 // TableName 获取对应数据库表名.
-func (m *Resource) TableName() string {
+func (m *ResourceModel) TableName() string {
 	return "resource"
 }
 
 // TableEngine 获取数据使用的引擎.
-func (m *Resource) TableEngine() string {
+func (m *ResourceModel) TableEngine() string {
 	return "INNODB"
 }
 
 // 多字段唯一键
-func (m *Resource) TableUnique() [][]string {
+func (m *ResourceModel) TableUnique() [][]string {
 	return [][]string{{"resource_group_id", "resource_name","action_name","http_method"}}
 }
 
-func (m *Resource) TableNameWithPrefix() string {
+func (m *ResourceModel) TableNameWithPrefix() string {
 	return conf.GetDatabasePrefix() + m.TableName()
 }
 
-func NewResource() *Resource {
-	return &Resource{}
+func NewResource() *ResourceModel {
+	return &ResourceModel{}
 }
 //添加或更新资源
-func (m *Resource) InsertOrUpdate(cols ...string) (err error) {
+func (m *ResourceModel) InsertOrUpdate(cols ...string) (err error) {
 	if m.ControllerName == "" || m.ActionName == "" || m.ResourceGroupId <= 0 || m.ResourceName == ""{
 		return errors.New("参数错误")
 	}
 	if m.HttpMethod == "" {
-		m.HttpMethod = "GET"
+		m.HttpMethod = "*"
 	}
 	o := orm.NewOrm()
-
+	resource := &ResourceModel{}
+	//如果设置了资源id,需要先查询是否真实存在
 	if m.ResourceId > 0 {
-		_,err = o.Update(m,cols...)
-	}else{
+		err = o.QueryTable(m.TableNameWithPrefix()).Filter("resource_id",m.ResourceId).One(resource)
+	}
+	//如果资源不存在,需要查询是否存在相同的资源
+	if err == nil {
+		err = o.QueryTable(m.TableNameWithPrefix()).Filter("controller_name",m.ControllerName).Filter("action_name",m.ActionName).Filter("http_method__in",[]string{"*",m.HttpMethod}).One(resource)
+		if err == nil {
+			return errors.New("资源已存在")
+		}
+	}
+
+	if err == orm.ErrNoRows {
 		_,err = o.Insert(m)
+	}else{
+		_,err = o.Update(m,cols...)
 	}
 	if err != nil {
 		beego.Error("添加或更新资源时出错 =>",err)
@@ -65,7 +77,7 @@ func (m *Resource) InsertOrUpdate(cols ...string) (err error) {
 }
 
 //删除资源
-func (m *Resource) Delete(resourceId int) (err error) {
+func (m *ResourceModel) Delete(resourceId int) (err error) {
 	o := orm.NewOrm()
 
 	_,err = o.QueryTable(m.TableNameWithPrefix()).Filter("resource_id",resourceId).Delete()

+ 2 - 4
models/member.go

@@ -29,15 +29,13 @@ type Member struct {
 	Email       string `orm:"size(100);column(email);unique" json:"email"`
 	Phone       string `orm:"size(255);column(phone);null;default(null)" json:"phone"`
 	Avatar      string `orm:"size(1000);column(avatar)" json:"avatar"`
-	//用户角色:0 超级管理员 /1 管理员/ 2 普通用户 .
+	//用户角色:1 超级管理员 /2 管理员/ 3 普通用户 .
 	Role          int        `orm:"column(role);type(int);default(1);index" json:"role"`
 	RoleName      string     `orm:"-" json:"role_name"`
 	Status        int        `orm:"column(status);type(int);default(0)" json:"status"` //用户状态:0 正常/1 禁用
 	CreateTime    time.Time  `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
 	CreateAt      int        `orm:"type(int);column(create_at)" json:"create_at"`
 	LastLoginTime time.Time  `orm:"type(datetime);column(last_login_time);null" json:"last_login_time"`
-	//用户权限列表
-	ResourceList []*Resource `orm:"-" json:"resource_list"`
 }
 
 // TableName 获取对应数据库表名.
@@ -153,7 +151,7 @@ func (m *Member) ldapLogin(account string, password string) (*Member, error) {
 func (m *Member) Add() error {
 	o := orm.NewOrm()
 
-	if ok, err := regexp.MatchString(conf.RegexpAccount, m.Account); m.Account == "" || !ok || err != nil {
+	if ok, err := regexp.MatchString(conf.RegexpAccount, m.Account); m.Account == "" || !ok || err != nil || m.Account == "anonymous" {
 		return errors.New("账号只能由英文字母数字组成,且在3-50个字符")
 	}
 	if m.Email == "" {

+ 2 - 2
routers/router.go

@@ -54,7 +54,7 @@ func init() {
 
 	beego.Router("/book", &controllers.BookController{}, "*:Index")
 	beego.Router("/book/:key/dashboard", &controllers.BookController{}, "*:Dashboard")
-	beego.Router("/book/:key/setting", &controllers.BookController{}, "*:Setting")
+	beego.Router("/book/:key/setting", &controllers.BookController{}, "get:Setting")
 	beego.Router("/book/:key/users", &controllers.BookController{}, "*:Users")
 	beego.Router("/book/:key/release", &controllers.BookController{}, "post:Release")
 	beego.Router("/book/:key/sort", &controllers.BookController{}, "post:SaveSort")
@@ -67,7 +67,7 @@ func init() {
 	beego.Router("/book/users/delete", &controllers.BookMemberController{}, "post:RemoveMember")
 	beego.Router("/book/users/import", &controllers.BookController{},"post:Import")
 
-	beego.Router("/book/setting/save", &controllers.BookController{}, "post:SaveBook")
+	beego.Router("/book/setting/save", &controllers.BookController{}, "post:Setting")
 	beego.Router("/book/setting/open", &controllers.BookController{}, "post:PrivatelyOwned")
 	beego.Router("/book/setting/transfer", &controllers.BookController{}, "post:Transfer")
 	beego.Router("/book/setting/upload", &controllers.BookController{}, "post:UploadCover")

+ 1 - 1
views/book/setting.tpl

@@ -53,7 +53,7 @@
                 </div>
                 <div class="box-body" style="padding-right: 200px;">
                     <div class="form-left">
-                        <form method="post" id="bookEditForm" action="{{urlfor "BookController.SaveBook"}}">
+                        <form method="post" id="bookEditForm" action="{{urlfor "BookController.Setting"}}">
                             <input type="hidden" name="identify" value="{{.Model.Identify}}">
                             <div class="form-group">
                                 <label>标题</label>