Skip to content

Commit

Permalink
feat: implement the Casbin Cloud Storage Adapter (#1)
Browse files Browse the repository at this point in the history
* chore: ignore .vscode folder from repo

* feat: implement basic adapter

* doc: add example

* ci: add semantic configuration

* ci: add Github Actions config

* doc: update README

* fix: add missing .releaserc.json file

* refactor: change ports to be sure that they are unused
  • Loading branch information
giefferre committed Mar 19, 2021
1 parent b1f16ee commit 3c7bbce
Show file tree
Hide file tree
Showing 12 changed files with 1,011 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/semantic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Always validate all commits, and ignore the PR title
commitsOnly: true
48 changes: 48 additions & 0 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Default

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go: [ 1.13, 1.14 ]
steps:
- uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}

- name: Run tests
run: make test

benchmark:
runs-on: ubuntu-latest
strategy:
matrix:
go: [ 1.13, 1.14 ]
steps:
- uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}

- name: Benchmark code
run: make benchmark

semantic-release:
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Run semantic-release
if: github.repository == 'qurami/casbin-cloud-storage-adapter' && github.event_name == 'push'
run: make release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@

# Dependency directories (remove the comment below to include it)
# vendor/

# IDE-specific
.vscode
16 changes: 16 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"debug": true,
"branches": [
"+([0-9])?(.{+([0-9]),x}).x",
"main",
{
"name": "beta",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
SHELL = /bin/bash
export PATH := $(shell yarn global bin):$(PATH)

default: test

test:
go test -race -cover -v .

benchmark:
go test -bench=.

release:
yarn global add semantic-release@17.2.4
semantic-release
79 changes: 77 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,77 @@
# casbin-cloud-storage-adapter
Casbin adapter implementation for GCP Cloud Storage
# Casbin Cloud Storage Adapter

[![Go Report Card](https://goreportcard.com/badge/github.com/qurami/casbin-cloud-storage-adapter)](https://goreportcard.com/report/github.com/qurami/casbin-cloud-storage-adapter)
[![Build Status](https://travis-ci.com/casbin/casbin.svg?branch=master)](https://travis-ci.com/casbin/casbin)
[![Coverage Status](https://coveralls.io/repos/github.com/qurami/casbin-cloud-storage-adapter/badge.svg?branch=master)](https://coveralls.io/github.com/qurami/casbin-cloud-storage-adapter?branch=master)
[![Godoc](https://godoc.org/github.com/qurami/casbin-cloud-storage-adapter?status.svg)](https://pkg.go.dev/github.com/qurami/casbin-cloud-storage-adapter/v1)

---

[Casbin](https://casbin.org/) adapter implementation for GCP Cloud Storage.
With this library, Casbin can load or save policies from/to Google Cloud Storage buckets.

## Installation

```
go get github.com/qurami/casbin-cloud-storage-adapter/v1
```

## Example Usage

```go
package main

import (
"context"
"log"

"cloud.google.com/go/storage"
"github.com/casbin/casbin/v2"
cloudstorageadapter "github.com/qurami/casbin-cloud-storage-adapter/v1"
)

func main() {
// Initialize a Google Cloud Storage client
// There are many ways, this is the quickest one.
// You could need a different one according to your configuration,
// please see https://pkg.go.dev/cloud.google.com/go/storage
cloudStorageClient, err := storage.NewClient(context.Background())
if err != nil {
log.Fatal(err)
}

// Create a new cloudstorageadapter.Adapter
adapter, err := cloudstorageadapter.NewAdapter(
cloudStorageClient,
"myBucketName",
"path/to/policies.csv",
)
if err != nil {
log.Fatal(err)
}

// Use the adapter in the casbin.NewEnforcer constructor
enforcer, err := casbin.NewEnforcer("rbac_model.conf", adapter)
if err != nil {
log.Fatal(err)
}

// Use the enforcer as usual
roles, err := enforcer.GetImplicitRolesForUser("alice")
if err != nil {
log.Fatal(err)
}

log.Println(roles)
}
```

The same file with the corresponding RBAC model is available in the [examples](examples) folder.

## Missing Features

This version is missing the _autosave_ features, so please remember to manually execute the `enforcer.SavePolicy` method when using this adapter.

## License

This project is under MIT License. See the [LICENSE](LICENSE) file for the full license text.
125 changes: 125 additions & 0 deletions adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package cloudstorageadapter

import (
"bufio"
"context"
"errors"
"fmt"
"io"
"strings"
"sync"

"cloud.google.com/go/storage"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist"
"github.com/casbin/casbin/v2/util"
)

// Adapter implements casbin/persist.Adapter
// storing policy configuration on a Google Cloud Storage Bucket
type Adapter struct {
client *storage.Client
bucketName string
objectKey string
context context.Context
mutex *sync.Mutex
}

// NewAdapter creates new Adapter
//
// Parameters:
// - client
// A cloud.google.com/go/storage.Client object
// - bucketName
// Name of the bucket where the policy configuration file is stored on
// - objectKey
// Key (name) of the object that contains policy configuration
func NewAdapter(client *storage.Client, bucketName string, objectKey string) (*Adapter, error) {
ctx := context.Background()
_, err := client.Bucket(bucketName).Attrs(ctx)
if err != nil {
return nil, err
}

adapter := Adapter{
client: client,
bucketName: bucketName,
objectKey: objectKey,
context: ctx,
mutex: new(sync.Mutex),
}
return &adapter, nil
}

// LoadPolicy loads policy from database.
func (a *Adapter) LoadPolicy(model model.Model) error {
a.mutex.Lock()
defer a.mutex.Unlock()

fileReader, err := a.client.Bucket(a.bucketName).Object(a.objectKey).NewReader(a.context)
if err != nil {
return err
}
defer fileReader.Close()

buf := bufio.NewReader(fileReader)
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
persist.LoadPolicyLine(line, model)
if err != nil {
if err == io.EOF {
break
}
return err
}
}
return nil
}

// SavePolicy saves all policy rules to the storage.
func (a *Adapter) SavePolicy(model model.Model) error {
a.mutex.Lock()
defer a.mutex.Unlock()

fileWriter := a.client.Bucket(a.bucketName).Object(a.objectKey).NewWriter(a.context)
defer fileWriter.Close()

for ptype, assertion := range model["p"] {
for _, rule := range assertion.Policy {
_, err := fileWriter.Write([]byte(fmt.Sprintf("%s, %s\n", ptype, util.ArrayToString(rule))))
if err != nil {
return err
}
}
}

for ptype, assertion := range model["g"] {
for _, rule := range assertion.Policy {
_, err := fileWriter.Write([]byte(fmt.Sprintf("%s, %s\n", ptype, util.ArrayToString(rule))))
if err != nil {
return err
}
}
}

return nil
}

// AddPolicy adds a policy rule to the storage.
// This is part of the Auto-Save feature.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}

// RemovePolicy removes a policy rule from the storage.
// This is part of the Auto-Save feature.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}

// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
// This is part of the Auto-Save feature.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return errors.New("not implemented")
}
Loading

0 comments on commit 3c7bbce

Please sign in to comment.