-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement casbin adapter for go-pg/pg
- Loading branch information
1 parent
3f155b6
commit 7fe62e4
Showing
10 changed files
with
782 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,65 @@ | ||
# casbin-pg-adapter | ||
A go-pg adapter for casbin | ||
Go-pg Adapter | ||
==== | ||
|
||
Go-pg Adapter is the [Go-pg](https://github.com/go-pg/pg) adapter for [Casbin](https://github.com/casbin/casbin). With this library, Casbin can load policy from PostgreSQL or save policy to it. | ||
|
||
## Installation | ||
|
||
go get github.com/MonedaCacao/casbin-pg-adapter | ||
|
||
## Env Variables | ||
|
||
Populate .env with necessary environment variable values: | ||
|
||
$ nano .env | ||
|
||
``` | ||
DATABASE_ADDRESSES= | ||
DATABASE_USER_NAME= | ||
DATABSE_USER_PASSORD= | ||
``` | ||
|
||
## Simple Postgres Example | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
pgadapter "github.com/MonedaCacao/casbin-pg-adapter" | ||
"github.com/casbin/casbin" | ||
) | ||
|
||
func main() { | ||
// Initialize a Go-pg adapter and use it in a Casbin enforcer: | ||
// The adapter will use the Postgres database named "casbin". | ||
// If it doesn't exist, the adapter will create it automatically. | ||
a, _ := pgadapter.NewAdapter() // Your driver and data source. | ||
|
||
// Or you can use an existing DB "abc" like this: | ||
// The adapter will use the table named "casbin_rule". | ||
// If it doesn't exist, the adapter will create it automatically. | ||
|
||
e := casbin.NewEnforcer("examples/rbac_model.conf", a) | ||
|
||
// Load the policy from DB. | ||
e.LoadPolicy() | ||
|
||
// Check the permission. | ||
e.Enforce("alice", "data1", "read") | ||
|
||
// Modify the policy. | ||
// e.AddPolicy(...) | ||
// e.RemovePolicy(...) | ||
|
||
// Save the policy back to DB. | ||
e.SavePolicy() | ||
} | ||
``` | ||
|
||
## Getting Help | ||
|
||
- [Casbin](https://github.com/casbin/casbin) | ||
|
||
## License | ||
|
||
This project is under Apache 2.0 License. See the [LICENSE](LICENSE) file for the full license text. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
package pgadapter | ||
|
||
import ( | ||
"github.com/MonedaCacao/casbin-pg-adapter/config" | ||
"github.com/casbin/casbin/model" | ||
"github.com/casbin/casbin/persist" | ||
"github.com/go-pg/pg" | ||
"github.com/go-pg/pg/orm" | ||
"log" | ||
"strings" | ||
) | ||
|
||
const ( | ||
tableExistsErrorCode = "ERROR #42P07" | ||
) | ||
|
||
// CasbinRule represents a rule in Casbin. | ||
type CasbinRule struct { | ||
Id int | ||
PType string | ||
V0 string | ||
V1 string | ||
V2 string | ||
V3 string | ||
V4 string | ||
V5 string | ||
} | ||
|
||
// Adapter represents the github.com/go-pg/pg adapter for policy storage. | ||
type Adapter struct { | ||
db *pg.DB | ||
} | ||
|
||
// finalizer is the destructor for Adapter. | ||
func finalizer(a *Adapter) {} | ||
|
||
// NewAdapter is the constructor for Adapter. | ||
// The adapter will automatically create a DB named "casbin" | ||
func NewAdapter() (*Adapter, error) { | ||
a := Adapter{} | ||
|
||
// Open the DB, create it if not existed. | ||
err := a.open() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &a, nil | ||
} | ||
|
||
func (a *Adapter) createDatabase() error { | ||
var err error | ||
var db *pg.DB | ||
|
||
env := config.GetEnvVariables() | ||
cfg := config.GetConfig(*env) | ||
|
||
db = pg.Connect(&pg.Options{ | ||
Addr: cfg.DatabaseAddresses, | ||
User: cfg.DatabaseUsername, | ||
Password: cfg.DatabseUserPassord, | ||
}) | ||
|
||
defer db.Close() | ||
|
||
_, err = db.Exec("CREATE DATABASE casbin") | ||
if err != nil { | ||
log.Println("can't create database", err) | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (a *Adapter) open() error { | ||
var err error | ||
var db *pg.DB | ||
|
||
env := config.GetEnvVariables() | ||
cfg := config.GetConfig(*env) | ||
|
||
err = a.createDatabase() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
db = pg.Connect(&pg.Options{ | ||
Addr: cfg.DatabaseAddresses, | ||
User: cfg.DatabaseUsername, | ||
Password: cfg.DatabseUserPassord, | ||
Database: "casbin", | ||
}) | ||
|
||
a.db = db | ||
|
||
return a.createTable() | ||
} | ||
|
||
func (a *Adapter) close() error { | ||
err := a.db.Close() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
a.db = nil | ||
return nil | ||
} | ||
|
||
func (a *Adapter) createTable() error { | ||
err := a.db.CreateTable(&CasbinRule{}, &orm.CreateTableOptions{ | ||
Temp: false, | ||
}) | ||
if err != nil { | ||
errorCode := err.Error()[0:12] | ||
if errorCode != tableExistsErrorCode { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (a *Adapter) dropTable() error { | ||
err := a.db.DropTable(&CasbinRule{}, &orm.DropTableOptions{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func loadPolicyLine(line *CasbinRule, model model.Model) { | ||
const prefixLine = ", " | ||
var sb strings.Builder | ||
|
||
sb.WriteString(line.PType) | ||
if len(line.V0) > 0 { | ||
sb.WriteString(prefixLine) | ||
sb.WriteString(line.V0) | ||
} | ||
if len(line.V1) > 0 { | ||
sb.WriteString(prefixLine) | ||
sb.WriteString(line.V1) | ||
} | ||
if len(line.V2) > 0 { | ||
sb.WriteString(prefixLine) | ||
sb.WriteString(line.V2) | ||
} | ||
if len(line.V3) > 0 { | ||
sb.WriteString(prefixLine) | ||
sb.WriteString(line.V3) | ||
} | ||
if len(line.V4) > 0 { | ||
sb.WriteString(prefixLine) | ||
sb.WriteString(line.V4) | ||
} | ||
if len(line.V5) > 0 { | ||
sb.WriteString(prefixLine) | ||
sb.WriteString(line.V5) | ||
} | ||
|
||
persist.LoadPolicyLine(sb.String(), model) | ||
} | ||
|
||
// LoadPolicy loads policy from database. | ||
func (a *Adapter) LoadPolicy(model model.Model) error { | ||
var lines []*CasbinRule | ||
|
||
if _, err := a.db.Query(&lines, `SELECT * FROM casbin_rules`); err != nil { | ||
return err | ||
} | ||
|
||
for _, line := range lines { | ||
loadPolicyLine(line, model) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func savePolicyLine(ptype string, rule []string) *CasbinRule { | ||
line := &CasbinRule{PType: ptype} | ||
|
||
l := len(rule) | ||
if l > 0 { | ||
line.V0 = rule[0] | ||
} | ||
if l > 1 { | ||
line.V1 = rule[1] | ||
} | ||
if l > 2 { | ||
line.V2 = rule[2] | ||
} | ||
if l > 3 { | ||
line.V3 = rule[3] | ||
} | ||
if l > 4 { | ||
line.V4 = rule[4] | ||
} | ||
if l > 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 | ||
} | ||
|
||
var lines []*CasbinRule | ||
|
||
for ptype, ast := range model["p"] { | ||
for _, rule := range ast.Policy { | ||
line := savePolicyLine(ptype, rule) | ||
lines = append(lines, line) | ||
} | ||
} | ||
|
||
for ptype, ast := range model["g"] { | ||
for _, rule := range ast.Policy { | ||
line := savePolicyLine(ptype, rule) | ||
lines = append(lines, line) | ||
} | ||
} | ||
|
||
err = a.db.Insert(&lines) | ||
return err | ||
} | ||
|
||
// 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.Insert(line) | ||
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 := a.db.Delete(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{PType: ptype} | ||
|
||
idx := fieldIndex + len(fieldValues) | ||
if fieldIndex <= 0 && idx > 0 { | ||
line.V0 = fieldValues[0-fieldIndex] | ||
} | ||
if fieldIndex <= 1 && idx > 1 { | ||
line.V1 = fieldValues[1-fieldIndex] | ||
} | ||
if fieldIndex <= 2 && idx > 2 { | ||
line.V2 = fieldValues[2-fieldIndex] | ||
} | ||
if fieldIndex <= 3 && idx > 3 { | ||
line.V3 = fieldValues[3-fieldIndex] | ||
} | ||
if fieldIndex <= 4 && idx > 4 { | ||
line.V4 = fieldValues[4-fieldIndex] | ||
} | ||
if fieldIndex <= 5 && idx > 5 { | ||
line.V5 = fieldValues[5-fieldIndex] | ||
} | ||
|
||
err := a.db.Delete(line) | ||
return err | ||
} |
Oops, something went wrong.