How to persist goRBAC instance

Introduction

goRBAC provides a lightweight role-based access control (RBAC) implementation in Golang. Normally, the privilege information (roles, parents, and permissions) are saved in the persistent storage, e.g. Database, Files, or Cloud Storage. This post will briefly discuss the technical details of how to load the goRBAC instance from persistent storage and how to save the instance back. In order to make things simple, I will use the JSON file as the persistent storage.

Preparation / Definition

Suppose we have 3 roles: Editor, Photographer and Chief Editor. Each role has the following parents and permissions:

Role Parents Permissions
editor NULL add-text, edit-text, insert-photo
photographer NULL add-photo, edit-photo
chief-editor editor, photographer del-text, del-photo

Converting the table into two JSON format. One saves the data of roles (`roles.json`), while another stores inheritance information (`inher.json`):

{"editor":["add-text","edit-text","insert-photo"],"photographer":["add-photo","edit-photo"],"chief-editor":["del-text","del-photo"]}
{"chief-editor":["editor","photographer"]}

Because it’s obvious enough, I won’t put any energy to a further explanation. However, if you got any question about this conversion, comments here are welcome.

Loading from JSON file

Loading the information and building goRBAC instance is simple.

First of all, open the JSON file and load all data into a map instance.

	// map[RoleId]PermissionIds
	var jsonRoles map[string][]string
	// map[RoleId]ParentIds
	var jsonInher map[string][]string
	// Load roles information
	if err := LoadJson("roles.json", &jsonRoles); err != nil {
		log.Fatal(err)
	}
	// Load inheritance information
	if err := LoadJson("inher.json", &jsonInher); err != nil {
		log.Fatal(err)
	}

Then we can start to build the goRBAC instance. There are 3 steps to build a goRBAC instance.

First of all, initialise the instance:

	rbac := gorbac.New()
	permissions := make(gorbac.Permissions)

And we can make `Role(s)` and add them into the goRBAC instance.

	// Build roles and add them to goRBAC instance
	for rid, pids := range jsonRoles {
		role := gorbac.NewStdRole(rid)
		for _, pid := range pids {
			_, ok := permissions[pid]
			if !ok {
				permissions[pid] = gorbac.NewStdPermission(pid)
			}
			role.Assign(permissions[pid])
		}
		rbac.Add(role)
	}

Following that, assign the inheritance relationship between roles.

	// Assign the inheritance relationship
	for rid, parents := range jsonInher {
		if err := rbac.SetParents(rid, parents); err != nil {
			log.Fatal(err)
		}
	}

Common use cases

After loading all the information and get the goRBAC instance on hand, we can start checking the privileges.
You can find the details here.

Saving back to JSON file

If we add some new roles and permissions or modify the relationship between the roles, then writing the changes back to the persistence storage is significant.

The easiest way is using the helper function `Walk`

	// Persist the change
	// map[RoleId]PermissionIds
	jsonOutputRoles := make(map[string][]string)
	// map[RoleId]ParentIds
	jsonOutputInher := make(map[string][]string)
	SaveJsonHandler := func(r gorbac.Role, parents []string) error {
		// WARNING: Don't use gorbac.RBAC instance in the handler,
		// otherwise it causes deadlock.
		permissions := make([]string, 0)
		for _, p := range r.(*gorbac.StdRole).Permissions() {
			permissions = append(permissions, p.ID())
		}
		jsonOutputRoles[r.ID()] = permissions
		jsonOutputInher[r.ID()] = parents
		return nil
	}
	if err := gorbac.Walk(rbac, SaveJsonHandler); err != nil {
		log.Fatalln(err)
	}

	// Save roles information
	if err := SaveJson("new-roles.json", &jsonOutputRoles); err != nil {
		log.Fatal(err)
	}
	// Save inheritance information
	if err := SaveJson("new-inher.json", &jsonOutputInher); err != nil {
		log.Fatal(err)
	}

One thing I have to mention is, despite it is a closure, DO NOT use the goRBAC instance in the handler. Otherwise, it will cause deadlock. The basic concept of the `WalkHandler` is grabbing the information from the goRBAC instance which is an READONLY process to the instance. Without any modify, no goRBAC instance access.

Conclution

goRBAC is following the basic idea of Go — Less is exponentially more and it’s inspired by “The Clean Architecture“. Thus the complex functions such as RDBMS interface, NoSQL interface, or authenticating won’t appear in the core. While `Helper` is an important part to make goRBAC easy to use.

We will be alive without help, but it won’t be easy. ;P

Learning it little by little. Extending it step by step.

Leave a Reply

Your email address will not be published. Required fields are marked *