Code refactoring is not an easy job, but it has to be done in most of the times. I just completed the lightweight role based access control library: goRBAC’s refactoring. There are some feedbacks and questions about the design and usage. I think it would be better writing something to share some design ideas and practice principles which will make things easier.
The master branch of current goRBAC’s source code is intended to be Version 2. While the previous version has been tagged as Version 1 with the v1.dev branch on Github. And this article will only discuss Version 2 (the master branch).
The new design separates goRBAC into 3 parts: core, interfaces & implements, and helper functions.
The core is the major part of goRBAC and manages inheritance relationship of roles. It contains only one struct `RBAC` with 8 methods. It’s easy to understand and use. What you need is reading the document.
`Role` is the only interface directly communicating with `RBAC`. It has two methods: `Id` returns the identity of a role and `Permit` checks if the permission is permitted to a role. For example, you can implement a Role named `God` and has all permissions as the God.
type God struct {} func(g *God) Id() string { return "God" } func(g *God) Permit(p Permission) bool { return true }
For making things easier, goRBAC has a built-in role `StdRole` which can be used in a real-life application and also be a good example for anyone who want to write their own `Role` implementation. Moreover, when you implement your own `Role`, struct embedding can be your good friend.
Eg.,
type MyOwnRole struct { gorbac.StdRole // extra fields } // extra functions
One thing is worth to be noticed, some methods in `Role` like AssignPermission, RevokePermission, Permissions etc. has been removed from the interface. Despite those methods don’t have any affection with `Role` and `RBAC`, they originally defined as a standard API and designed for some data persistence task. I decided to remove them after I joined “Go 1.6 Global Release Party – Auckland Zone” with some great discussions. “Less is exponentially more” again!
Another important interface is `Permission` which is only communicating with `Role`. It also has two methods: `Id` and `Match`. `Id` is the identity of a permission. `Match` is used to detect if current permission matches another one.
The library also supplies two built-in structs for `Permission`.
First one is `StdPermission` which uses `Id` to detect if two permissions are matching.
Another one is `LayerPermission` which supports multi-layers matching and can be very useful to design something like access management of control panels. To be exact, permission “admin” matches “admin:article” and “admin:article:delete”; “admin:article” matches “admin:article:delete”, but you will get a negative result by reversing matching.
And when you have some “hard mode” access control, implementing your own `Permission` is a good idea.
Eg.,
type FuzzPermission struct { IdStr string } func(f *FuzzPermission) Id() string { return f.IdStr } func(f *FuzzPermission) Permit(p Permission) bool { if f.IdStr == p.Id() { // exactly matched return true } q, ok := p.(*FuzzPermission) if !ok { // Not same type return false } return strings.Contains(f.IdStr, p.Id()) }
Well, I’m not sure what circumstance will use this kind of permissions, but you can have it. 😛
There are also three built-in helper functions: `AllGranted`, `AnyGranted` and `InherCircle`. As they are named, you can use this functions to do some common jobs. They might combine into `RBAC` itself, but it will increase the complex of API and the list of those functions may growth longer and longer. Thus, a set of individual functions is the best choice. You can learn more from the test case.
I think you should have some sort of ideas about goRBAC. The last thing I’d like to mention is “Why goRBAC do NOT have any persistence mechanisms”. In the version 1, I did design something to persistent data, i.e., `Restore`, `Dump`, and `Map`. But with yearly practicals, I found it’s impossible to “DO ONCE FIT EVERYWHERE”. I had to redesign and modify the code in every different project. I had to load the data from different storage like MongoDB, MySQL, XML and text files and converted it into a uniform struct. After that, I can `Restore` the uniform struct into an RBAC instance. Well, it’s more convenient there are no need to write `Restore` code to build an RBAC instance, but I had to write loading and converting code repeatedly. So, why we don’t load data and build the RBAC instance directly? Users response for their own data model, while goRBAC manages access control greatly. That’s enough!
I didn’t put a lot of examples here. I think most of the time ideas is more important than that. My bad English writing aside, if you have any questions and suggestions about goRBAC, please feel free to contact me or post an issue on Github.
Thanks & happy coding!
Leave a Reply