Skip to content

Commit

Permalink
store: add redis policy store (#40)
Browse files Browse the repository at this point in the history
Signed-off-by: Son Dinh <[email protected]>
  • Loading branch information
115100 authored and arekkas committed Nov 21, 2016
1 parent dbcb044 commit 3938d61
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 8 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ In contrast to [ACL](https://en.wikipedia.org/wiki/Access_control_list) and [RBA
you get fine-grained access control with the ability to answer questions in complex environments such as multi-tenant or distributed applications
and large organizations. Ladon is inspired by [AWS IAM Policies](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html).

Ladon ships with storage adapters for SQL (officially supported: MySQL, PostgreSQL) and RethinkDB (community supported).
Ladon ships with storage adapters for SQL (officially supported: MySQL, PostgreSQL), Redis and RethinkDB (community supported).

**[Hydra](https://github.com/ory-am/hydra)**, an OAuth2 and OpenID Connect implementation uses Ladon for access control.

Expand Down Expand Up @@ -409,7 +409,7 @@ func main() {
#### Persistence

Obviously, creating such a policy is not enough. You want to persist it too. Ladon ships an interface `ladon.Manager` for
this purpose with default implementations for In-Memory, RethinkDB and SQL (PostgreSQL, MySQL). Let's take a look how to
this purpose with default implementations for In-Memory, RethinkDB, SQL (PostgreSQL, MySQL) and Redis. Let's take a look how to
instantiate those.

**In-Memory**
Expand Down Expand Up @@ -455,6 +455,31 @@ func main() {
}
```

**Redis**

```go
import (
"github.com/ory-am/ladon"
"gopkg.in/redis.v5"
)

func main () {
db = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})

if err := db.Ping().Err(); err != nil {
log.Fatalf("Could not connect to database: %s". err)
}

warden := ladon.Ladon{
Manager: ladon.NewRedisManager(db, "redis_key_prefix:")
}

// ...
}
```

### Access Control (Warden)

Now that we have defined our policies, we can use the warden to check if a request is valid.
Expand Down
14 changes: 12 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import:
- context
- package: gopkg.in/dancannon/gorethink.v2
version: ~2.1.3
- package: gopkg.in/redis.v5
version: ^5.0.0
testImport:
- package: github.com/stretchr/testify
version: ~1.1.3
Expand Down
111 changes: 111 additions & 0 deletions manager_redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ladon

import (
"encoding/json"

"github.com/pkg/errors"
"gopkg.in/redis.v5"
)

// RedisManager is a redis implementation of Manager to store policies persistently.
type RedisManager struct {
db *redis.Client
keyPrefix string
}

// NewRedisManager initializes a new RedisManager with no policies
func NewRedisManager(db *redis.Client, keyPrefix string) *RedisManager {
return &RedisManager{
db: db,
keyPrefix: keyPrefix,
}
}

const redisPolicies = "ladon:policies"

var (
redisPolicyExists = errors.New("Policy exists")
redisNotFound = errors.New("Not found")
)

func (m *RedisManager) redisPoliciesKey() string {
return m.keyPrefix + redisPolicies
}

// Create a new policy to RedisManager
func (m *RedisManager) Create(policy Policy) error {
payload, err := json.Marshal(policy)
if err != nil {
return errors.Wrap(err, "policy marshal failed")
}

wasKeySet, err := m.db.HSetNX(m.redisPoliciesKey(), policy.GetID(), string(payload)).Result()
if !wasKeySet {
return errors.Wrap(redisPolicyExists, "")
} else if err != nil {
return errors.Wrap(err, "policy creation failed")
}

return nil
}

// Get retrieves a policy.
func (m *RedisManager) Get(id string) (Policy, error) {
resp, err := m.db.HGet(m.redisPoliciesKey(), id).Bytes()
if err == redis.Nil {
return nil, redisNotFound
} else if err != nil {
return nil, errors.Wrap(err, "")
}

return redisUnmarshalPolicy(resp)
}

// Delete removes a policy.
func (m *RedisManager) Delete(id string) error {
if err := m.db.HDel(m.redisPoliciesKey(), id).Err(); err != nil {
return errors.Wrap(err, "policy deletion failed")
}

return nil
}

// FindPoliciesForSubject finds all policies associated with the subject.
func (m *RedisManager) FindPoliciesForSubject(subject string) (Policies, error) {
var ps Policies

iter := m.db.HScan(m.redisPoliciesKey(), 0, "", 0).Iterator()
for iter.Next() {
if !iter.Next() {
break
}
resp := []byte(iter.Val())

p, err := redisUnmarshalPolicy(resp)
if err != nil {
return nil, err
}

if ok, err := Match(p, p.GetSubjects(), subject); err != nil {
return nil, errors.Wrap(err, "policy subject match failed")
} else if !ok {
continue
}

ps = append(ps, p)
}
if err := iter.Err(); err != nil {
return nil, errors.Wrap(err, "")
}

return ps, nil
}

func redisUnmarshalPolicy(policy []byte) (Policy, error) {
var p *DefaultPolicy
if err := json.Unmarshal(policy, &p); err != nil {
return nil, errors.Wrap(err, "policy unmarshal failed")
}

return p, nil
}
28 changes: 24 additions & 4 deletions manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import (
"github.com/pborman/uuid"
"github.com/stretchr/testify/assert"
//"github.com/stretchr/testify/require"
"golang.org/x/net/context"
r "gopkg.in/dancannon/gorethink.v2"
. "github.com/ory-am/ladon"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
. "github.com/ory-am/ladon"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
r "gopkg.in/dancannon/gorethink.v2"
"gopkg.in/redis.v5"
)

var managerPolicies = []*DefaultPolicy{
Expand Down Expand Up @@ -110,6 +111,7 @@ func TestMain(m *testing.M) {
connectMEM()
connectPG()
connectMySQL()
connectRedis()

retCode := m.Run()
os.Exit(retCode)
Expand Down Expand Up @@ -204,6 +206,24 @@ func connectRDB() {
managers["rethink"] = rethinkManager
}

func connectRedis() {
var db *redis.Client
c, err := dockertest.ConnectToRedis(15, time.Second, func(url string) bool {
db = redis.NewClient(&redis.Options{
Addr: url,
})

return db.Ping().Err() == nil
})

if err != nil {
log.Fatalf("Could not connect to database: %s", err)
}

containers = append(containers, c)
managers["redis"] = NewRedisManager(db, "")
}

func TestColdStart(t *testing.T) {
assert.Nil(t, rethinkManager.Create(&DefaultPolicy{ID: "foo", Description: "description foo"}))
assert.Nil(t, rethinkManager.Create(&DefaultPolicy{ID: "bar", Description: "description bar"}))
Expand Down

0 comments on commit 3938d61

Please sign in to comment.