Skip to content

Commit

Permalink
Plugin permissoin service (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
ostcar authored Nov 28, 2020
1 parent 9c31f7f commit c355046
Show file tree
Hide file tree
Showing 17 changed files with 228 additions and 79 deletions.
6 changes: 3 additions & 3 deletions cmd/autoupdate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"path"
"syscall"

"github.com/OpenSlides/openslides-permission-service/pkg/permission"
"github.com/openslides/openslides-autoupdate-service/internal/auth"
"github.com/openslides/openslides-autoupdate-service/internal/autoupdate"
"github.com/openslides/openslides-autoupdate-service/internal/datastore"
Expand Down Expand Up @@ -93,14 +94,13 @@ func run() error {
}

// Perm Service.
perms := &test.MockPermission{}
perms.Default = true
perms := permission.New(datastoreService)

// Restricter Service.
restricter := restrict.New(perms, restrict.RelationChecker(restrict.RelationLists, perms))

// Autoupdate Service.
service := autoupdate.New(datastoreService, restricter, closed)
service := autoupdate.New(datastoreService, restricter, perms, closed)

// Auth Service.
authService, err := buildAuth(env, r, closed, errHandler)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.15

require (
github.com/OpenSlides/openslides-models-to-go v0.1.1-0.20201023163752-f3a92dde2a27
github.com/OpenSlides/openslides-permission-service v0.0.0-20201106150223-db52cf71b584
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
github.com/gomodule/redigo v1.8.3
github.com/ostcar/topic v0.3.4-0.20200613094955-61bb28837a98
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/OpenSlides/openslides-models-to-go v0.1.1-0.20201023163752-f3a92dde2a27 h1:hKpGuicCgWOrbDHU6+1vsPJgzbzX1Y1el5XaeIUZMFY=
github.com/OpenSlides/openslides-models-to-go v0.1.1-0.20201023163752-f3a92dde2a27/go.mod h1:CriCefW5smTixhFfVLiuA8NgyMX4PAU5e3YpJHnaZx8=
github.com/OpenSlides/openslides-permission-service v0.0.0-20201106150223-db52cf71b584 h1:5Fv6W0+eyuD4TkL+I4WjOjM2TSkX+m24vIQq9utPGO0=
github.com/OpenSlides/openslides-permission-service v0.0.0-20201106150223-db52cf71b584/go.mod h1:VPzKimi8Jz5Qdu5oeAWYRFLThXRP6pnueH/9K8wILDE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
Expand Down
19 changes: 17 additions & 2 deletions internal/autoupdate/autoupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import (
// value means, that more memory is used.
const pruneTime = 10 * time.Minute

// Format of keys in the topic that shows, that a full update is necessary. It
// is in the same namespace then model names. So make sure, there is no model
// with this name.
const fullUpdateFormat = "fullupdate/%d"

// Autoupdate holds the state of the autoupdate service. It has to be initialized
// with autoupdate.New().
type Autoupdate struct {
Expand All @@ -30,7 +35,7 @@ type Autoupdate struct {
}

// New creates a new autoupdate service.
func New(datastore Datastore, restricter Restricter, closed <-chan struct{}) *Autoupdate {
func New(datastore Datastore, restricter Restricter, userUdater UserUpdater, closed <-chan struct{}) *Autoupdate {
a := &Autoupdate{
datastore: datastore,
restricter: restricter,
Expand All @@ -43,6 +48,16 @@ func New(datastore Datastore, restricter Restricter, closed <-chan struct{}) *Au
for k := range data {
keys = append(keys, k)
}

uids, err := userUdater.AdditionalUpdate(context.TODO(), data)
if err != nil {
return fmt.Errorf("getting addition user ids: %w", err)
}

for _, uid := range uids {
keys = append(keys, fmt.Sprintf(fullUpdateFormat, uid))
}

a.topic.Publish(keys...)
return nil
})
Expand Down Expand Up @@ -125,7 +140,7 @@ func (a *Autoupdate) RestrictedData(ctx context.Context, uid int, keys ...string
data[key] = values[i]
}

if err := a.restricter.Restrict(uid, data); err != nil {
if err := a.restricter.Restrict(ctx, uid, data); err != nil {
return nil, fmt.Errorf("restrict data: %w", err)
}
return data, nil
Expand Down
2 changes: 1 addition & 1 deletion internal/autoupdate/autoupdate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func TestLive(t *testing.T) {
datastore := new(test.MockDatastore)
closed := make(chan struct{})
defer close(closed)
s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)
kb := test.KeysBuilder{K: []string{"foo", "bar"}}

ctx, cancel := context.WithCancel(context.Background())
Expand Down
78 changes: 46 additions & 32 deletions internal/autoupdate/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,7 @@ type Connection struct {
// this case, nil is returned.
func (c *Connection) Next(ctx context.Context) (map[string]json.RawMessage, error) {
if c.filter == nil {
// First time called
c.filter = new(filter)
if c.tid == 0 {
c.tid = c.autoupdate.topic.LastID()
}

if err := c.kb.Update(ctx); err != nil {
return nil, fmt.Errorf("create keys for keysbuilder: %w", err)
}

data, err := c.autoupdate.RestrictedData(ctx, c.uid, c.kb.Keys()...)
if err != nil {
return nil, fmt.Errorf("get first time restricted data: %w", err)
}

// Delete empty values in first responce.
for k, v := range data {
if len(v) == 0 {
delete(data, k)
}
}

if err := c.filter.filter(data); err != nil {
return nil, fmt.Errorf("filter data for the first time: %w", err)
}

return data, nil
return c.allData(ctx)
}

var err error
Expand All @@ -60,6 +34,21 @@ func (c *Connection) Next(ctx context.Context) (map[string]json.RawMessage, erro
return nil, fmt.Errorf("get updated keys: %w", err)
}

changedSlice := make(map[string]bool, len(changedKeys))
for _, key := range changedKeys {
var uid int
if _, err := fmt.Sscanf(key, fullUpdateFormat, &uid); err == nil {
// The key is a fullUpdate key. Do not use it, excpect of a full
// update.
if uid == c.uid {
return c.allData(ctx)
}
continue
}

changedSlice[key] = true
}

oldKeys := c.kb.Keys()

// Update keysbuilder get new list of keys
Expand All @@ -70,11 +59,6 @@ func (c *Connection) Next(ctx context.Context) (map[string]json.RawMessage, erro
// Start with keys hat are new for the user
keys := keysDiff(oldKeys, c.kb.Keys())

changedSlice := make(map[string]bool, len(changedKeys))
for _, key := range changedKeys {
changedSlice[key] = true
}

// Append keys that are old but have been changed.
for _, key := range oldKeys {
if !changedSlice[key] {
Expand Down Expand Up @@ -107,6 +91,36 @@ func (c *Connection) Next(ctx context.Context) (map[string]json.RawMessage, erro
return data, nil
}

func (c *Connection) allData(ctx context.Context) (map[string]json.RawMessage, error) {
// First time called
c.filter = new(filter)
if c.tid == 0 {
c.tid = c.autoupdate.topic.LastID()
}

if err := c.kb.Update(ctx); err != nil {
return nil, fmt.Errorf("create keys for keysbuilder: %w", err)
}

data, err := c.autoupdate.RestrictedData(ctx, c.uid, c.kb.Keys()...)
if err != nil {
return nil, fmt.Errorf("get first time restricted data: %w", err)
}

// Delete empty values in first responce.
for k, v := range data {
if len(v) == 0 {
delete(data, k)
}
}

if err := c.filter.filter(data); err != nil {
return nil, fmt.Errorf("filter data for the first time: %w", err)
}

return data, nil
}

func keysDiff(old []string, new []string) []string {
keySet := make(map[string]bool, len(old))
for _, key := range old {
Expand Down
101 changes: 96 additions & 5 deletions internal/autoupdate/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"testing"
"time"

"github.com/openslides/openslides-autoupdate-service/internal/autoupdate"
"github.com/openslides/openslides-autoupdate-service/internal/test"
Expand Down Expand Up @@ -86,7 +87,7 @@ func TestConnectionEmptyData(t *testing.T) {
closed := make(chan struct{})
defer close(closed)

s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)

kb := test.KeysBuilder{K: test.Str(doesExistKey, doesNotExistKey)}

Expand Down Expand Up @@ -189,7 +190,7 @@ func TestConnectionFilterData(t *testing.T) {

closed := make(chan struct{})
defer close(closed)
s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)
kb := test.KeysBuilder{K: test.Str("user/1/name")}
c := s.Connect(1, kb)
if _, err := c.Next(context.Background()); err != nil {
Expand All @@ -214,7 +215,7 @@ func TestConntectionFilterOnlyOneKey(t *testing.T) {
datastore := new(test.MockDatastore)
closed := make(chan struct{})
close(closed)
s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)
kb := test.KeysBuilder{K: test.Str("user/1/name")}
c := s.Connect(1, kb)
if _, err := c.Next(context.Background()); err != nil {
Expand All @@ -239,12 +240,102 @@ func TestConntectionFilterOnlyOneKey(t *testing.T) {
}
}

func TestFullUpdate(t *testing.T) {
datastore := new(test.MockDatastore)
closed := make(chan struct{})
defer close(closed)
userUpdater := new(mockUserUpdater)
s := autoupdate.New(datastore, new(test.MockRestricter), userUpdater, closed)
kb := test.KeysBuilder{K: test.Str("user/1/name")}

t.Run("other user", func(t *testing.T) {
c := s.Connect(1, kb)
if _, err := c.Next(context.Background()); err != nil {
t.Errorf("c.Next() returned an error: %v", err)
}

// send fulldata for other user
userUpdater.userIDs = []int{2}
datastore.Send(test.Str("some/5/data"))

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var data map[string]json.RawMessage
var err error
isBlocking := blocking(func() {
data, err = c.Next(ctx)
})

if !isBlocking {
t.Fatalf("fulldataupdate did not block")
}

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

if len(data) != 0 {
t.Errorf("Got %v, expected no key update", data)
}
})

t.Run("same user", func(t *testing.T) {
c := s.Connect(1, kb)
if _, err := c.Next(context.Background()); err != nil {
t.Errorf("c.Next() returned an error: %v", err)
}

// Send fulldata for same user.
userUpdater.userIDs = []int{1}
datastore.Send(test.Str("some/5/data"))

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var data map[string]json.RawMessage
var err error
isBlocking := blocking(func() {
data, err = c.Next(ctx)
})

if isBlocking {
t.Fatalf("fulldataupdate did block")
}

if err != nil {
t.Errorf("Got unexpected error: %v", err)
}

if len(data) != 1 || string(data["user/1/name"]) != `"Hello World"` {
t.Errorf("Got %v, expected [user/1/name: Hello World]", data)
}
})
}

func blocking(f func()) bool {
done := make(chan struct{})
go func() {
f()
close(done)
}()

timer := time.NewTimer(time.Millisecond)
defer timer.Stop()
select {
case <-done:
return false
case <-timer.C:
return true
}
}

func BenchmarkFilterChanging(b *testing.B) {
const keyCount = 100
datastore := new(test.MockDatastore)
closed := make(chan struct{})
defer close(closed)
s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)

keys := make([]string, 0, keyCount)
for i := 0; i < keyCount; i++ {
Expand All @@ -271,7 +362,7 @@ func BenchmarkFilterNotChanging(b *testing.B) {
datastore := new(test.MockDatastore)
closed := make(chan struct{})
defer close(closed)
s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)

keys := make([]string, 0, keyCount)
for i := 0; i < keyCount; i++ {
Expand Down
2 changes: 1 addition & 1 deletion internal/autoupdate/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestFeatures(t *testing.T) {
datastore.OnlyData = true
closed := make(chan struct{})
defer close(closed)
s := autoupdate.New(datastore, new(test.MockRestricter), closed)
s := autoupdate.New(datastore, new(test.MockRestricter), mockUserUpdater{}, closed)

for _, tt := range []struct {
name string
Expand Down
7 changes: 6 additions & 1 deletion internal/autoupdate/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ type Datastore interface {
// Restricter restricts keys.
type Restricter interface {
// Restrict manipulates the values for the user with the given id.
Restrict(uid int, data map[string]json.RawMessage) error
Restrict(ctx context.Context, uid int, data map[string]json.RawMessage) error
}

// KeysBuilder holds the keys that are requested by a user.
type KeysBuilder interface {
Update(ctx context.Context) error
Keys() []string
}

// UserUpdater has a function to get user_ids, that should get a full update.
type UserUpdater interface {
AdditionalUpdate(ctx context.Context, updated map[string]json.RawMessage) ([]int, error)
}
Loading

0 comments on commit c355046

Please sign in to comment.