Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Go

on:
pull_request:
branches:
- main
push:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: 1.25
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8

- name: Format
uses: Jerome1337/[email protected]

- name: Run tests
run: go test -v ./...
182 changes: 181 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,181 @@
# rbac
# RBAC - Role-Based Access Control

This project implements a flexible and powerful Role-Based Access Control (RBAC) system for Go applications. It provides hierarchical role management, dynamic permission evaluation through business rules, and supports multiple storage backends.

## Installation

- To use the provided package:

```bash
import "github.com/codescalers/rbac/pkg"
```

First import the package in your Go file

- Then get the package by running this command in CLI:

```bash
go get github.com/codescalers/rbac
```

- Initialize the RBAC system:

```go
// Setup database
db, _ := gorm.Open(sqlite.Open("rbac.db"), &gorm.Config{})
store, _ := store.NewGormStore(db)

// Create RBAC instance
r, _ := rbac.NewRBAC(context.Background(), store)
```

## Usage

The RBAC system provides a simple and intuitive API for managing roles, permissions, and subjects. Here's how to use the main features:

### Example: Blog Access Control

```go
ctx := context.Background()

// 1. Create roles with hierarchy
userRole, _ := r.CreateRole(ctx, "user", "Regular user")
adminRole, _ := r.CreateRole(ctx, "admin", "Administrator", "user")

// 2. Create permissions
readPerm, _ := r.CreatePermission(ctx, "blog", "read")
writePerm, _ := r.CreatePermission(ctx, "blog", "write")
deletePerm, _ := r.CreatePermission(ctx, "blog", "delete")

// 3. Assign permissions to roles
r.AddPermissionToRole(ctx, "user", readPerm.ID)
r.AddPermissionToRole(ctx, "admin", writePerm.ID)
r.AddPermissionToRole(ctx, "admin", deletePerm.ID)

// 4. Create subjects with roles
r.CreateSubjectWithRole(ctx, "user-123", "user")
r.CreateSubjectWithRole(ctx, "admin-456", "admin")

// 5. Check permissions
type Blog struct {
ID string
Title string
}

func (b Blog) Name() string { return "blog" }

blog := Blog{ID: "1", Title: "My Post"}

// User can read (has direct permission)
canRead, _ := r.Can(ctx, "user-123", "read", blog)
fmt.Println("User can read:", canRead) // true

// User cannot delete (doesn't have permission)
canDelete, _ := r.Can(ctx, "user-123", "delete", blog)
fmt.Println("User can delete:", canDelete) // false

// Admin can read (inherited from user role)
canRead, _ = r.Can(ctx, "admin-456", "read", blog)
fmt.Println("Admin can read:", canRead) // true

// Admin can delete (has direct permission)
canDelete, _ = r.Can(ctx, "admin-456", "delete", blog)
fmt.Println("Admin can delete:", canDelete) // true
```

### Using Business Rules

Business rules allow dynamic permission evaluation based on resource context (e.g., ownership):

```go
// Define a business rule
type OwnershipRule struct{}

func (r OwnershipRule) Name() string {
return "ownership"
}

func (r OwnershipRule) Evaluate(ctx context.Context, subjectID string, resource rbac.Resource) (bool, error) {
blog, ok := resource.(BlogPost)
if !ok {
return false, fmt.Errorf("expected BlogPost")
}
return blog.OwnerID == subjectID, nil
}

// Register the rule
r.RegisterBizRule(rbac.BizRule(OwnershipRule{}))

// Create permission with business rule
updateOwnPerm, _ := r.CreatePermission(ctx, "blog", "update", "ownership")

// Add to role
r.AddPermissionToRole(ctx, "user", updateOwnPerm.ID)

// Check permission
type BlogPost struct {
ID string
Title string
OwnerID string
}

func (b BlogPost) Name() string { return "blog" }

myPost := BlogPost{ID: "1", Title: "My Post", OwnerID: "user-123"}
otherPost := BlogPost{ID: "2", Title: "Other Post", OwnerID: "user-789"}

// User can update their own post
canUpdate, _ := r.Can(ctx, "user-123", "update", myPost)
fmt.Println("Can update own post:", canUpdate) // true

// User cannot update others' posts
canUpdate, _ = r.Can(ctx, "user-123", "update", otherPost)
fmt.Println("Can update other post:", canUpdate) // false
```

### Role Hierarchy

Child roles automatically inherit all permissions from their parent roles:

```go
// Create hierarchy: viewer <- editor <- admin
viewer, _ := r.CreateRole(ctx, "viewer", "Can view content")
editor, _ := r.CreateRole(ctx, "editor", "Can edit content", "viewer")
admin, _ := r.CreateRole(ctx, "admin", "Full access", "editor")

// Assign permissions
readPerm, _ := r.CreatePermission(ctx, "document", "read")
editPerm, _ := r.CreatePermission(ctx, "document", "edit")
deletePerm, _ := r.CreatePermission(ctx, "document", "delete")

r.AddPermissionToRole(ctx, "viewer", readPerm.ID)
r.AddPermissionToRole(ctx, "editor", editPerm.ID)
r.AddPermissionToRole(ctx, "admin", deletePerm.ID)

// Create users
r.CreateSubjectWithRole(ctx, "user-1", "viewer")
r.CreateSubjectWithRole(ctx, "user-2", "editor")
r.CreateSubjectWithRole(ctx, "user-3", "admin")

// viewer: can only read
// editor: can read (inherited) + edit
// admin: can read (inherited) + edit (inherited) + delete
```

For detailed API documentation, see [API Reference](./docs/API.md).

## Storage Backends

The library includes comprehensive tests with over 45 test cases:

```bash
# Run all tests
go test ./pkg/... -v

# Run tests with coverage
go test ./pkg/... -cover
```

## License

MIT License - see [LICENSE](LICENSE) file for details.
171 changes: 171 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package main

import (
"context"
"fmt"
"log"

rbac "github.com/codescalers/rbac/pkg"
"github.com/codescalers/rbac/pkg/store"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

// Blog represents a blog post resource
type Blog struct {
ID string
Title string
Content string
OwnerID string
}

// Name implements the rbac.Resource interface
func (b Blog) Name() string {
return "blog"
}

// BlogOwnershipRule ensures users can only access their own blogs
type BlogOwnershipRule struct{}

func (r BlogOwnershipRule) Name() string {
return "blog_ownership"
}

func (r BlogOwnershipRule) Evaluate(ctx context.Context, subjectID string, resource rbac.Resource) (bool, error) {
blog, ok := resource.(Blog)
if !ok {
return false, fmt.Errorf("expected Blog resource, got %T", resource)
}

// Allow access if the user is the owner
return blog.OwnerID == subjectID, nil
}

func main() {
ctx := context.Background()

// Initialize SQLite database
db, err := gorm.Open(sqlite.Open("rbac.db"), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}

// Create GORM store
gormStore, err := store.NewGormStore(db)
if err != nil {
log.Fatal("Failed to create store:", err)
}

// Initialize RBAC
r, err := rbac.NewRBAC(ctx, gormStore)
if err != nil {
log.Fatal(err)
}

// Register business rule for blog ownership
bizRole := rbac.BizRule(BlogOwnershipRule{})
if err := r.RegisterBizRule(bizRole); err != nil {
log.Fatal(err)
}

// Create permissions
readPerm, err := r.CreatePermission(ctx, "blog", "read")
if err != nil {
log.Fatal(err)
}
updateOwnPerm, err := r.CreatePermission(ctx, "blog", "update", bizRole.Name())
if err != nil {
log.Fatal(err)
}
deleteOwnPerm, err := r.CreatePermission(ctx, "blog", "delete", bizRole.Name())
if err != nil {
log.Fatal(err)
}
createPerm, err := r.CreatePermission(ctx, "blog", "create")
if err != nil {
log.Fatal(err)
}

updateAll, err := r.CreatePermission(ctx, "blog", "update")
if err != nil {
log.Fatal(err)
}
deleteAll, err := r.CreatePermission(ctx, "blog", "delete")
if err != nil {
log.Fatal(err)
}

// Create roles
userRole, err := r.CreateRole(ctx, "user", "Regular user with blog access")
if err != nil {
log.Fatal(err)
}
adminRole, err := r.CreateRole(ctx, "admin", "Administrator with full access", "user")
if err != nil {
log.Fatal(err)
}

fmt.Printf("Created roles: user=%s, admin=%s\n", userRole.ID, adminRole.ID)

//Add user permissions
if err := r.AddPermissionToRole(ctx, "user", readPerm.ID); err != nil {
log.Fatal(err)
}
if err := r.AddPermissionToRole(ctx, "user", createPerm.ID); err != nil {
log.Fatal(err)
}
if err := r.AddPermissionToRole(ctx, "user", updateOwnPerm.ID); err != nil {
log.Fatal(err)
}
if err := r.AddPermissionToRole(ctx, "user", deleteOwnPerm.ID); err != nil {
log.Fatal(err)
}

//Add admin permissions
if err := r.AddPermissionToRole(ctx, "admin", updateAll.ID); err != nil {
log.Fatal(err)
}
if err := r.AddPermissionToRole(ctx, "admin", deleteAll.ID); err != nil {
log.Fatal(err)
}

// Create test users
adminUserID := "admin-user-123"
regularUserID := "regular-user-456"

// Create subjects with roles using role names
if err := r.CreateSubjectWithRole(ctx, adminUserID, "admin"); err != nil {
log.Fatal(err)
}
if err := r.CreateSubjectWithRole(ctx, regularUserID, "user"); err != nil {
log.Fatal(err)
}

fmt.Println("Created subjects with roles")

// Test blogs
blog1 := Blog{ID: "blog-1", Title: "Admin's Blog", OwnerID: adminUserID}
blog2 := Blog{ID: "blog-2", Title: "User's Blog", OwnerID: regularUserID}

hasPerm, err := r.Can(ctx, regularUserID, "update", blog2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User has permission to update blog2: %v\n", hasPerm)
hasPerm, err = r.Can(ctx, regularUserID, "update", blog1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User has permission to update blog1: %v\n", hasPerm)

hasPerm, err = r.Can(ctx, adminUserID, "update", blog1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Admin has permission to update blog1: %v\n", hasPerm)
hasPerm, err = r.Can(ctx, adminUserID, "update", blog2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Admin has permission to update blog2: %v\n", hasPerm)
}
Loading