整合casbin添加adapter处理器

This commit is contained in:
yxh
2019-12-24 23:38:36 +08:00
commit 5eb00597f1
31 changed files with 788 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
*.js linguist-language=GO
*.css linguist-language=GO
*.html linguist-language=GO
+21
View File
@@ -0,0 +1,21 @@
.buildpath
.hgignore.swp
.project
.orig
.swp
.idea/
.settings/
.vscode/
vender/
log/
composer.lock
gitpush.sh
pkg/
bin/
cbuild
*/.DS_Store
config/config.toml
main
.vscode
go.sum
*.exe
+3
View File
@@ -0,0 +1,3 @@
# GoFrame Project
https://goframe.org
+10
View File
@@ -0,0 +1,10 @@
package hello
import (
"github.com/gogf/gf/net/ghttp"
)
// Hello World
func Handler(r *ghttp.Request) {
r.Response.Writeln("Hello World!")
}
+81
View File
@@ -0,0 +1,81 @@
package user
import (
"gfast/app/service/user"
"gfast/library/response"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/util/gvalid"
)
//用户API管理对象
type Controller struct {}
//用户注册接口
func (c Controller) SignUp(r *ghttp.Request) {
if err:=user.SignUp(r.GetPostMapStrStr());err!=nil{
response.Json(r, 1, err.Error())
}else{
response.Json(r, 0, "ok")
}
}
// 用户登录接口
func (c *Controller) SignIn(r *ghttp.Request) {
data := r.GetPostMapStrStr()
rules := map[string]string{
"passport": "required",
"password": "required",
}
msgs := map[string]interface{}{
"passport": "账号不能为空",
"password": "密码不能为空",
}
if e := gvalid.CheckMap(data, rules, msgs); e != nil {
response.Json(r, 1, e.String())
}
if err := user.SignIn(data["passport"], data["password"], r.Session); err != nil {
response.Json(r, 1, err.Error())
} else {
response.Json(r, 0, "ok")
}
}
// 判断用户是否已经登录
func (c *Controller) IsSignedIn(r *ghttp.Request) {
if user.IsSignedIn(r.Session) {
response.Json(r, 0, "ok")
} else {
response.Json(r, 1, "")
}
}
// 用户注销/退出接口
func (c *Controller) SignOut(r *ghttp.Request) {
user.SignOut(r.Session)
response.Json(r, 0, "ok")
}
// 检测用户账号接口(唯一性校验)
func (c *Controller) CheckPassport(r *ghttp.Request) {
passport := r.GetString("passport")
if e := gvalid.Check(passport, "required", "请输入账号"); e != nil {
response.Json(r, 1, e.String())
}
if user.CheckPassport(passport) {
response.Json(r, 0, "ok")
}
response.Json(r, 1, "账号已经存在")
}
// 检测用户昵称接口(唯一性校验)
func (c *Controller) CheckNickName(r *ghttp.Request) {
nickname := r.Get("nickname")
if e := gvalid.Check(nickname, "required", "请输入昵称"); e != nil {
response.Json(r, 1, e.String())
}
if user.CheckNickName(r.GetString("nickname")) {
response.Json(r, 0, "ok")
}
response.Json(r, 1, "昵称已经存在")
}
View File
View File
+91
View File
@@ -0,0 +1,91 @@
package user
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gvalid"
)
const USER_SESSION_MARK = "user_info"
var (
//表对象
table = g.DB().Table("user").Safe()
)
//用户注册
func SignUp(data g.MapStrStr)error{
//数据校验
rules:=[]string{
"passport @required|length:6,16#账号不能为空|账号长度应在:min到:max之间",
"password2@required|length:6,16#请输入确认密码|密码长度应当在:min到:max之间",
"password @required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等",
}
if err:=gvalid.CheckMap(data,rules);err!=nil{
return errors.New(err.String())
}
if _, ok := data["nickname"]; !ok {
data["nickname"] = data["passport"]
}
// 唯一性数据检查
if !CheckPassport(data["passport"]) {
return errors.New(fmt.Sprintf("账号 %s 已经存在", data["passport"]))
}
if !CheckNickName(data["nickname"]) {
return errors.New(fmt.Sprintf("昵称 %s 已经存在", data["nickname"]))
}
// 记录账号创建/注册时间
if _, ok := data["create_time"]; !ok {
data["create_time"] = gtime.Now().String()
}
if _, err := table.Filter().Data(data).Save(); err != nil {
return err
}
return nil
}
// 判断用户是否已经登录
func IsSignedIn(session *ghttp.Session) bool {
return session.Contains(USER_SESSION_MARK)
}
// 用户登录,成功返回用户信息,否则返回nil; passport应当会md5值字符串
func SignIn(passport, password string, session *ghttp.Session) error {
record, err := table.Where("passport=? and password=?", passport, password).One()
if err != nil && err != sql.ErrNoRows {
return err
}
if record == nil {
return errors.New("账号或密码错误")
}
session.Set(USER_SESSION_MARK, record)
return nil
}
// 用户注销
func SignOut(session *ghttp.Session) {
session.Remove(USER_SESSION_MARK)
}
// 检查账号是否符合规范(目前仅检查唯一性),存在返回false,否则true
func CheckPassport(passport string) bool {
if i, err := table.Where("passport", passport).Count(); err != nil && err != sql.ErrNoRows {
return false
} else {
return i == 0
}
}
// 检查昵称是否符合规范(目前仅检查唯一性),存在返回false,否则true
func CheckNickName(nickname string) bool {
if i, err := table.Where("nickname", nickname).Count(); err != nil && err != sql.ErrNoRows {
return false
} else {
return i == 0
}
}
View File
+8
View File
@@ -0,0 +1,8 @@
package boot
import "github.com/gogf/gf/frame/g"
func init() {
g.Server().SetPort(8200)
}
View File
View File
+9
View File
@@ -0,0 +1,9 @@
module gfast
require (
github.com/casbin/casbin/v2 v2.1.2
github.com/gogf/gf v1.10.0
google.golang.org/appengine v1.6.5 // indirect
)
go 1.13
View File
+29
View File
@@ -0,0 +1,29 @@
package response
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
// 标准返回结果数据结构封装。
// 返回固定数据结构的JSON:
// err: 错误码(0:成功, 1:失败, >1:错误码);
// msg: 请求结果信息;
// data: 请求结果,根据不同接口返回结果的数据结构不同;
func Json(r *ghttp.Request, err int, msg string, data ...interface{}) {
responseData := interface{}(nil)
if len(data) > 0 {
responseData = data[0]
}
r.Response.WriteJson(g.Map{
"err": err,
"msg": msg,
"data": responseData,
})
}
// 返回JSON数据并退出当前HTTP执行函数。
func JsonExit(r *ghttp.Request, err int, msg string, data ...interface{}) {
Json(r, err, msg, data...)
r.Exit()
}
+279
View File
@@ -0,0 +1,279 @@
package utils
import (
"fmt"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist"
"github.com/gogf/gf/database/gdb"
"runtime"
)
type CasbinRule struct {
PType string `json:"ptype"`
V0 string `json:"v0"`
V1 string `json:"v1"`
V2 string `json:"v2"`
V3 string `json:"v3"`
V4 string `json:"v4"`
V5 string `json:"v5"`
}
// Adapter represents the gdb adapter for policy storage.
type Adapter struct {
DriverName string
DataSourceName string
TableName string
Db gdb.DB
}
// finalizer is the destructor for Adapter.
func finalizer(a *Adapter) {
// 注意不用的时候不需要使用Close方法关闭数据库连接(并且gdb也没有提供Close方法),
// 数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭
a.Db = nil
}
// NewAdapter is the constructor for Adapter.
func NewAdapter(driverName string, dataSourceName string) (*Adapter, error) {
a := &Adapter{}
a.DriverName = driverName
a.DataSourceName = dataSourceName
a.TableName = "casbin_rule"
// Open the DB, create it if not existed.
err := a.open()
if err != nil {
return nil, err
}
// Call the destructor when the object is released.
runtime.SetFinalizer(a, finalizer)
return a, nil
}
// NewAdapterFromOptions is the constructor for Adapter with existed connection
func NewAdapterFromOptions(adapter *Adapter) (*Adapter, error) {
if adapter.TableName == "" {
adapter.TableName = "casbin_rule"
}
if adapter.Db == nil {
err := adapter.open()
if err != nil {
return nil, err
}
runtime.SetFinalizer(adapter, finalizer)
}
return adapter, nil
}
func (a *Adapter) open() error {
var err error
var db gdb.DB
gdb.SetConfig(gdb.Config{
"casbin": gdb.ConfigGroup{
gdb.ConfigNode{
Type: a.DriverName,
LinkInfo: a.DataSourceName,
Role: "master",
Weight: 100,
},
},
})
db, err = gdb.New("casbin")
if err != nil {
return err
}
a.Db = db
return a.createTable()
}
func (a *Adapter) close() error {
// 注意不用的时候不需要使用Close方法关闭数据库连接(并且gdb也没有提供Close方法),
// 数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭
a.Db = nil
return nil
}
func (a *Adapter) createTable() error {
_, err := a.Db.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (ptype VARCHAR(10), v0 VARCHAR(256), v1 VARCHAR(256), v2 VARCHAR(256), v3 VARCHAR(256), v4 VARCHAR(256), v5 VARCHAR(256))", a.TableName))
return err
}
func (a *Adapter) dropTable() error {
_, err := a.Db.Exec(fmt.Sprintf("DROP TABLE %s", a.TableName))
return err
}
func loadPolicyLine(line CasbinRule, model model.Model) {
lineText := line.PType
if line.V0 != "" {
lineText += ", " + line.V0
}
if line.V1 != "" {
lineText += ", " + line.V1
}
if line.V2 != "" {
lineText += ", " + line.V2
}
if line.V3 != "" {
lineText += ", " + line.V3
}
if line.V4 != "" {
lineText += ", " + line.V4
}
if line.V5 != "" {
lineText += ", " + line.V5
}
persist.LoadPolicyLine(lineText, model)
}
// LoadPolicy loads policy from database.
func (a *Adapter) LoadPolicy(model model.Model) error {
var lines []CasbinRule
if err := a.Db.Table(a.TableName).Scan(&lines); err != nil {
return err
}
for _, line := range lines {
loadPolicyLine(line, model)
}
return nil
}
func savePolicyLine(ptype string, rule []string) CasbinRule {
line := CasbinRule{}
line.PType = ptype
if len(rule) > 0 {
line.V0 = rule[0]
}
if len(rule) > 1 {
line.V1 = rule[1]
}
if len(rule) > 2 {
line.V2 = rule[2]
}
if len(rule) > 3 {
line.V3 = rule[3]
}
if len(rule) > 4 {
line.V4 = rule[4]
}
if len(rule) > 5 {
line.V5 = rule[5]
}
return line
}
// SavePolicy saves policy to database.
func (a *Adapter) SavePolicy(model model.Model) error {
err := a.dropTable()
if err != nil {
return err
}
err = a.createTable()
if err != nil {
return err
}
for ptype, ast := range model["p"] {
for _, rule := range ast.Policy {
line := savePolicyLine(ptype, rule)
_, err := a.Db.Table(a.TableName).Data(&line).Insert()
if err != nil {
return err
}
}
}
for ptype, ast := range model["g"] {
for _, rule := range ast.Policy {
line := savePolicyLine(ptype, rule)
_, err := a.Db.Table(a.TableName).Data(&line).Insert()
if err != nil {
return err
}
}
}
return nil
}
// AddPolicy adds a policy rule to the storage.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
line := savePolicyLine(ptype, rule)
_, err := a.Db.Table(a.TableName).Data(&line).Insert()
return err
}
// RemovePolicy removes a policy rule from the storage.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
line := savePolicyLine(ptype, rule)
err := rawDelete(a, line)
return err
}
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
line := CasbinRule{}
line.PType = ptype
if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) {
line.V0 = fieldValues[0-fieldIndex]
}
if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) {
line.V1 = fieldValues[1-fieldIndex]
}
if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) {
line.V2 = fieldValues[2-fieldIndex]
}
if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) {
line.V3 = fieldValues[3-fieldIndex]
}
if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) {
line.V4 = fieldValues[4-fieldIndex]
}
if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) {
line.V5 = fieldValues[5-fieldIndex]
}
err := rawDelete(a, line)
return err
}
func rawDelete(a *Adapter, line CasbinRule) error {
db := a.Db.Table(a.TableName)
db.Where("ptype = ?", line.PType)
if line.V0 != "" {
db.Where("v0 = ?", line.V0)
}
if line.V1 != "" {
db.Where("v1 = ?", line.V1)
}
if line.V2 != "" {
db.Where("v2 = ?", line.V2)
}
if line.V3 != "" {
db.Where("v3 = ?", line.V3)
}
if line.V4 != "" {
db.Where("v4 = ?", line.V4)
}
if line.V5 != "" {
db.Where("v5 = ?", line.V5)
}
_, err := db.Delete()
return err
}
+11
View File
@@ -0,0 +1,11 @@
package main
import(
_ "gfast/boot"
_ "gfast/router"
"github.com/gogf/gf/frame/g"
)
func main() {
g.Server().Run()
}
+24
View File
@@ -0,0 +1,24 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
func main() {
// 基本事件回调使用
p := "/:name/info/{uid}"
s := g.Server()
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_SERVE) },
ghttp.HOOK_AFTER_SERVE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_SERVE) },
ghttp.HOOK_BEFORE_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_OUTPUT) },
ghttp.HOOK_AFTER_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_OUTPUT) },
})
s.BindHandler(p, func(r *ghttp.Request) {
r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
})
s.SetPort(8199)
s.Run()
}
View File
View File
View File
View File
View File
View File
+13
View File
@@ -0,0 +1,13 @@
package router
import (
"gfast/app/api/hello"
"gfast/app/api/user"
"github.com/gogf/gf/frame/g"
)
// 统一路由注册.
func init() {
g.Server().BindHandler("/", hello.Handler)
g.Server().BindObject("/user", new(user.Controller))
}
View File
+15
View File
@@ -0,0 +1,15 @@
# Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
+2
View File
@@ -0,0 +1,2 @@
p, alice, data1, read
p, bob, data2, write
1 p alice data1 read
2 p bob data2 write
+14
View File
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
+5
View File
@@ -0,0 +1,5 @@
p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
1 p, alice, data1, read
2 p, bob, data2, write
3 p, data2_admin, data2, read
4 p, data2_admin, data2, write
5 g, alice, data2_admin
+170
View File
@@ -0,0 +1,170 @@
package test
import (
"fmt"
"gfast/library/utils"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/util"
"github.com/gogf/gf/os/glog"
"testing"
)
func TestDemo(t *testing.T){
//t.Run("demo1" ,Demo1)
t.Run("Adapters_test", Adapters)
}
func Demo1(t *testing.T){
e,err:=casbin.NewEnforcer("casbin_conf/model.conf","casbin_conf/policy.csv")
if err!=nil{
panic(err)
}
sub := "alice" // the user that wants to access a resource. 对象
obj := "data1" // the resource that is going to be accessed. 资源
act := "write" // the operation that the user performs on the resource. 操作
ok, err := e.Enforce(sub, obj, act)
if err != nil {
fmt.Println("验证失败",err)
}
if ok == true {
fmt.Println("权限通过")
} else {
fmt.Println("没有权限")
}
}
func Adapters(t *testing.T) {
a := initAdapter(t, "mysql", "root:123456@tcp(127.0.0.1:3306)/test2")
testAutoSave(t, a)
testSaveLoad(t, a)
a = initAdapterFormOptions(t, &utils.Adapter{
DriverName: "mysql",
DataSourceName: "root:123456@tcp(127.0.0.1:3306)/test2",
})
testAutoSave(t, a)
testSaveLoad(t, a)
}
func initAdapterFormOptions(t *testing.T, adapter *utils.Adapter) *utils.Adapter {
// Create an adapter
a, _ := utils.NewAdapterFromOptions(adapter)
// Initialize some policy in DB.
initPolicy(t, a)
// Now the DB has policy, so we can provide a normal use case.
// Note: you don't need to look at the above code
// if you already have a working DB with policy inside.
return a
}
func initPolicy(t *testing.T, a *utils.Adapter) {
// Because the DB is empty at first,
// so we need to load the policy from the file adapter (.CSV) first.
e, err := casbin.NewEnforcer("casbin_conf/rbac_model.conf", "casbin_conf/rbac_policy.csv")
if err != nil {
panic(err)
}
// This is a trick to save the current policy to the DB.
// We can't call e.SavePolicy() because the adapter in the enforcer is still the file adapter.
// The current policy means the policy in the Casbin enforcer (aka in memory).
err = a.SavePolicy(e.GetModel())
if err != nil {
panic(err)
}
// Clear the current policy.
e.ClearPolicy()
testGetPolicy(t, e, [][]string{})
// Load the policy from DB.
err = a.LoadPolicy(e.GetModel())
if err != nil {
panic(err)
}
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
}
func testGetPolicy(t *testing.T, e *casbin.Enforcer, res [][]string) {
myRes := e.GetPolicy()
glog.Info("Policy: ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Policy: ", myRes, ", supposed to be ", res)
}
}
func initAdapter(t *testing.T, driverName string, dataSourceName string) *utils.Adapter {
// Create an adapter
a, err := utils.NewAdapter(driverName, dataSourceName)
if err != nil {
panic(err)
}
// Initialize some policy in DB.
initPolicy(t, a)
// Now the DB has policy, so we can provide a normal use case.
// Note: you don't need to look at the above code
// if you already have a working DB with policy inside.
return a
}
func testAutoSave(t *testing.T, a *utils.Adapter) {
// NewEnforcer() will load the policy automatically.
e, err := casbin.NewEnforcer("casbin_conf/rbac_model.conf", a)
if err!=nil{
panic(err)
}
// AutoSave is enabled by default.
// Now we disable it.
e.EnableAutoSave(false)
// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
// it doesn't affect the policy in the storage.
e.AddPolicy("alice", "data1", "write")
// Reload the policy from the storage to see the effect.
e.LoadPolicy()
// This is still the original policy.
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
// Now we enable the AutoSave.
e.EnableAutoSave(true)
// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
// but also affects the policy in the storage.
e.AddPolicy("alice", "data1", "write")
// Reload the policy from the storage to see the effect.
e.LoadPolicy()
// The policy has a new rule: {"alice", "data1", "write"}.
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}, {"alice", "data1", "write"}})
// Remove the added rule.
e.RemovePolicy("alice", "data1", "write")
e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
// Remove "data2_admin" related policy rules via a filter.
// Two rules: {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"} are deleted.
e.RemoveFilteredPolicy(0, "data2_admin")
e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}})
}
func testSaveLoad(t *testing.T, a *utils.Adapter) {
// Initialize some policy in DB.
initPolicy(t, a)
// Note: you don't need to look at the above code
// if you already have a working DB with policy inside.
// Now the DB has policy, so we can provide a normal use case.
// Create an adapter and an enforcer.
// NewEnforcer() will load the policy automatically.
e, _ := casbin.NewEnforcer("casbin_conf/rbac_model.conf", a)
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
}