diff --git a/acl/acl.go b/acl/acl.go new file mode 100644 index 00000000..7ea752d6 --- /dev/null +++ b/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 +} diff --git a/acl/resource.go b/acl/resource.go new file mode 100644 index 00000000..41637d73 --- /dev/null +++ b/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) + } +} + + + + + + + + + + + + + + + + + + diff --git a/commands/command.go b/commands/command.go index 790a9882..4533cbfa 100644 --- a/commands/command.go +++ b/commands/command.go @@ -97,7 +97,6 @@ func RegisterModel() { new(models.Label), new(models.MemberGroup), new(models.MemberGroupMembers), - new(models.Resource), ) //migrate.RegisterMigration() } diff --git a/commands/install.go b/commands/install.go index dc1c2e74..675f12bd 100644 --- a/commands/install.go +++ b/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 = "admin@iminho.me" 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) + } } diff --git a/controllers/AccountController.go b/controllers/AccountController.go index 8cdfc2e8..f4ed1b41 100644 --- a/controllers/AccountController.go +++ b/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, "验证码已过期,请重新操作。") } diff --git a/controllers/BaseController.go b/controllers/BaseController.go index 824c475b..b916c241 100644 --- a/controllers/BaseController.go +++ b/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 - } + //如果没有访问权限 + 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()))) { - resourceList,err := models.NewMemberGroup().FindMemberGroupResourceList(roleId) - if err != nil { - beego.Error("获取用户许可资源时出错 =>", err) - c.ShowErrorPage(500,"获取用户许可资源时出错") + c.ShowErrorPage(403,"权限不足") } - c.MemberResourceList = resourceList - c.Data["MemberResource"] = resourceList - - for _,resource := range resourceList { - if resource.ControllerName == controller && resource.ActionName == action && resource.HttpMethod == c.Ctx.Input.Method() { - return - } - } - 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) + } } } diff --git a/controllers/BookController.go b/controllers/BookController.go index 15f79169..8cd6ac75 100644 --- a/controllers/BookController.go +++ b/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() diff --git a/main.go b/main.go index 115f4847..96b33045 100644 --- a/main.go +++ b/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" ) diff --git a/models/MemberGroupModel.go b/models/MemberGroupModel.go index f05213f4..f0dcd4c2 100644 --- a/models/MemberGroupModel.go +++ b/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) diff --git a/models/ResourceModel.go b/models/ResourceModel.go index 8ed68ca6..271a8d2f 100644 --- a/models/ResourceModel.go +++ b/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() diff --git a/models/member.go b/models/member.go index 3a14ea6e..97cc6af9 100644 --- a/models/member.go +++ b/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 == "" { diff --git a/routers/router.go b/routers/router.go index 3cc986d1..21f0a4a6 100644 --- a/routers/router.go +++ b/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") diff --git a/views/book/setting.tpl b/views/book/setting.tpl index 9933f557..27f70357 100644 --- a/views/book/setting.tpl +++ b/views/book/setting.tpl @@ -53,7 +53,7 @@