-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement the Casbin Cloud Storage Adapter (#1)
* 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
Showing
12 changed files
with
1,011 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,6 @@ | |
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
# IDE-specific | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 [email protected] | ||
semantic-release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
Oops, something went wrong.