| Gorm Adapter |
| ==== |
| |
| > In v3.0.3, method `NewAdapterByDB` creates table named `casbin_rules`, |
| > we fix it to `casbin_rule` after that. |
| > If you used v3.0.3 and less, and you want to update it, |
| > you might need to *migrate* data manually. |
| > Find out more at: https://github.com/casbin/gorm-adapter/issues/78 |
| |
| [](https://goreportcard.com/report/github.com/casbin/gorm-adapter) |
| [](https://github.com/casbin/gorm-adapter/actions/workflows/ci.yml) |
| [](https://coveralls.io/github/casbin/gorm-adapter?branch=master) |
| [](https://godoc.org/github.com/casbin/gorm-adapter) |
| [](https://github.com/casbin/gorm-adapter/releases/latest) |
| [](https://discord.gg/S5UjpzGZjN) |
| [](https://sourcegraph.com/github.com/casbin/gorm-adapter?badge) |
| |
| Gorm Adapter is the [Gorm](https://gorm.io/gorm) adapter for [Casbin](https://github.com/casbin/casbin). With this library, Casbin can load policy from Gorm supported database or save policy to it. |
| |
| Based on [Officially Supported Databases](https://v1.gorm.io/docs/connecting_to_the_database.html#Supported-Databases), The current supported databases are: |
| |
| - MySQL |
| - PostgreSQL |
| - SQL Server |
| - Sqlite3 |
| > gorm-adapter use ``github.com/glebarez/sqlite`` instead of gorm official sqlite driver ``gorm.io/driver/sqlite`` because the latter needs ``cgo`` support. But there is almost no difference between the two driver. If there is a difference in use, please submit an issue. |
| |
| - other 3rd-party supported DBs in Gorm website or other places. |
| |
| ## Installation |
| |
| go get github.com/casbin/gorm-adapter/v3 |
| |
| ## Simple Example |
| |
| ```go |
| package main |
| |
| import ( |
| "github.com/casbin/casbin/v2" |
| gormadapter "github.com/casbin/gorm-adapter/v3" |
| _ "github.com/go-sql-driver/mysql" |
| ) |
| |
| func main() { |
| // Initialize a Gorm adapter and use it in a Casbin enforcer: |
| // The adapter will use the MySQL database named "casbin". |
| // If it doesn't exist, the adapter will create it automatically. |
| // You can also use an already existing gorm instance with gormadapter.NewAdapterByDB(gormInstance) |
| a, _ := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") // Your driver and data source. |
| e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a) |
| |
| // 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. |
| // a := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/abc", true) |
| |
| // 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() |
| } |
| ``` |
| |
| ## Simple PostgreSQL Example |
| |
| ```go |
| package main |
| |
| import ( |
| "github.com/casbin/casbin/v2" |
| gormadapter "github.com/casbin/gorm-adapter/v3" |
| _ "github.com/lib/pq" |
| ) |
| |
| func main() { |
| // Initialize a Gorm adapter and use it in a Casbin enforcer: |
| // The adapter will use the PostgreSQL database named "casbin". |
| // If it doesn't exist, the adapter will create it automatically. |
| // You can also use an already existing gorm instance with gormadapter.NewAdapterByDB(gormInstance) |
| a, _ := gormadapter.NewAdapter("postgres", "host=127.0.0.1 port=5432 user=postgres password=postgres sslmode=disable") // Your driver and data source. |
| e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a) |
| |
| // 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. |
| // a := gormadapter.NewAdapter("postgres", "host=127.0.0.1 port=5432 user=postgres password=postgres dbname=abc sslmode=disable", true) |
| |
| // 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() |
| } |
| ``` |
| |
| ## Turn off AutoMigrate |
| New an adapter will use ``AutoMigrate`` by default for create table, if you want to turn it off, please use API ``TurnOffAutoMigrate(db *gorm.DB) *gorm.DB``. See example: |
| ```go |
| db, err := gorm.Open(mysql.Open("root:@tcp(127.0.0.1:3306)/casbin"), &gorm.Config{}) |
| TurnOffAutoMigrate(db) |
| // a,_ := NewAdapterByDB(...) |
| // a,_ := NewAdapterByDBUseTableName(...) |
| a,_ := NewAdapterByDBWithCustomTable(...) |
| ``` |
| Find out more details at [gorm-adapter#162](https://github.com/casbin/gorm-adapter/issues/162) |
| ## Customize table columns example |
| You can change the gorm struct tags, but the table structure must stay the same. |
| ```go |
| package main |
| |
| import ( |
| "github.com/casbin/casbin/v2" |
| gormadapter "github.com/casbin/gorm-adapter/v3" |
| "gorm.io/gorm" |
| ) |
| |
| func main() { |
| // Increase the column size to 512. |
| type CasbinRule struct { |
| ID uint `gorm:"primaryKey;autoIncrement"` |
| Ptype string `gorm:"size:512;uniqueIndex:unique_index"` |
| V0 string `gorm:"size:512;uniqueIndex:unique_index"` |
| V1 string `gorm:"size:512;uniqueIndex:unique_index"` |
| V2 string `gorm:"size:512;uniqueIndex:unique_index"` |
| V3 string `gorm:"size:512;uniqueIndex:unique_index"` |
| V4 string `gorm:"size:512;uniqueIndex:unique_index"` |
| V5 string `gorm:"size:512;uniqueIndex:unique_index"` |
| } |
| |
| db, _ := gorm.Open(...) |
| |
| // Initialize a Gorm adapter and use it in a Casbin enforcer: |
| // The adapter will use an existing gorm.DB instnace. |
| a, _ := gormadapter.NewAdapterByDBWithCustomTable(db, &CasbinRule{}) |
| 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() |
| } |
| ``` |
| ## Transaction |
| |
| You can modify policies within a transaction. See the example below: |
| |
| ```go |
| // The new implementation uses a two-phase commit protocol and supports multi-threaded environments. |
| |
| package main |
| |
| func main() { |
| adapter, _ := gormadapter.NewTransactionalAdapterByDB(db) |
| e, _ := casbin.NewTransactionalEnforcer("examples/rbac_model.conf", adapter) |
| |
| ctx := context.Background() |
| |
| // WithTransaction executes a function within a transaction. |
| // If the function returns an error, the transaction is rolled back. |
| // Otherwise, it's committed automatically. |
| err := e.WithTransaction(ctx, func(tx *casbin.Transaction) error { |
| tx.AddPolicy("alice", "data1", "read") |
| tx.AddPolicy("alice", "data1", "write") |
| return nil |
| }) |
| |
| // If you wish to manually handle the transaction |
| tx, _ := e.BeginTransaction(ctx) |
| tx.AddPolicy("alice", "data1", "write") |
| if err := tx.Commit(); err != nil { |
| // handle if transaction failed |
| } |
| } |
| ``` |
| |
| ```go |
| // old verion |
| package main |
| |
| func main() { |
| a, err := NewAdapterByDB(db) |
| e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a) |
| err = e.GetAdapter().(*Adapter).Transaction(e, func(e casbin.IEnforcer) error { |
| _, err := e.AddPolicy("jack", "data1", "write") |
| if err != nil { |
| return err |
| } |
| _, err = e.AddPolicy("jack", "data2", "write") |
| if err != nil { |
| return err |
| } |
| return nil |
| }) |
| if err != nil { |
| // handle if transaction failed |
| return |
| } |
| } |
| ``` |
| ## ConditionsToGormQuery |
| |
| `ConditionsToGormQuery()` is a function that converts multiple query conditions into a GORM query statement |
| You can use the `GetAllowedObjectConditions()` API of Casbin to get conditions, |
| and choose the way of combining conditions through `combineType`. |
| |
| `ConditionsToGormQuery()` allows Casbin to be combined with SQL, and you can use it to implement many functions. |
| ### Example: GetAllowedRecordsForUser |
| * model example: [object_conditions_model.conf](examples/object_conditions_model.conf) |
| * policy example: [object_conditions_policy.csv](examples/object_conditions_policy.csv) |
| |
| DataBase example: |
| |
| |id|title|author|publisher|publish_data|price|category_id| |
| |--|--|--|--|--|--|--| |
| |1|book1|author1|publisher1|2023-04-09 16:23:42|10|1| |
| |2|book2|author1|publisher1|2023-04-09 16:23:44|20|2| |
| |3|book3|author2|publisher1|2023-04-09 16:23:44|30|1| |
| |4|book4|author2|publisher2|2023-04-09 16:23:45|10|3| |
| |5|book5|author3|publisher2|2023-04-09 16:23:45|50|1| |
| |6|book6|author3|publisher2|2023-04-09 16:23:46|60|2| |
| |
| |
| ```go |
| type Book struct { |
| ID int |
| Title string |
| Author string |
| Publisher string |
| PublishDate time.Time |
| Price float64 |
| CategoryID int |
| } |
| |
| func TestGetAllowedRecordsForUser(t *testing.T) { |
| e, _ := casbin.NewEnforcer("examples/object_conditions_model.conf", "examples/object_conditions_policy.csv") |
| |
| conditions, err := e.GetAllowedObjectConditions("alice", "read", "r.obj.") |
| if err != nil { |
| panic(err) |
| } |
| fmt.Println(conditions) |
| |
| dsn := "root:root@tcp(127.0.0.1:3307)/test?charset=utf8mb4&parseTime=True&loc=Local" |
| db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) |
| if err != nil { |
| panic(err) |
| } |
| |
| fmt.Println("CombineTypeOr") |
| rows, err := ConditionsToGormQuery(db, conditions, CombineTypeOr).Model(&Book{}).Rows() |
| defer rows.Close() |
| var b Book |
| for rows.Next() { |
| err := db.ScanRows(rows, &b) |
| if err != nil { |
| panic(err) |
| } |
| log.Println(b) |
| } |
| |
| fmt.Println("CombineTypeAnd") |
| rows, err = ConditionsToGormQuery(db, conditions, CombineTypeAnd).Model(&Book{}).Rows() |
| defer rows.Close() |
| for rows.Next() { |
| err := db.ScanRows(rows, &b) |
| if err != nil { |
| panic(err) |
| } |
| log.Println(b) |
| } |
| } |
| ``` |
| |
| ## Context Adapter |
| |
| `gormadapter` supports adapter with context, the following is a timeout control implemented using context |
| |
| ```go |
| a, _ := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") // Your driver and data source. |
| // Limited time 300s |
| ctx, cancel := context.WithTimeout(context.Background(), 300*time.Microsecond) |
| defer cancel() |
| err := a.AddPolicyCtx(ctx, "p", "p", []string{"alice", "data1", "read"}) |
| if err != nil { |
| panic(err) |
| } |
| ``` |
| |
| ## 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. |