实现简单的权限控制

dev
Minho 2018-05-08 17:28:49 +08:00
parent 6fb801d7ba
commit f7fc11ed7c
13 changed files with 253 additions and 51 deletions

31
acl/acl.go 100644
View File

@ -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
acl/resource.go 100644
View File

@ -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)
}
}

View File

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

View File

@ -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)
}
}

View File

@ -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, "验证码已过期,请重新操作。")
}

View File

@ -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)
}
}
}

View File

@ -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()

View File

@ -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"
)

View File

@ -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)

View File

@ -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()

View File

@ -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 == "" {

View File

@ -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")

View File

@ -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>