Skip to content

Commit

Permalink
Merge pull request #238 from aenario/permissions-persistence
Browse files Browse the repository at this point in the history
Permissions persistence
  • Loading branch information
jinroh committed Jan 24, 2017
2 parents 88d8005 + aff1c19 commit 606a0e8
Show file tree
Hide file tree
Showing 16 changed files with 285 additions and 96 deletions.
16 changes: 5 additions & 11 deletions pkg/apps/apps.go
Expand Up @@ -49,12 +49,6 @@ const (
// either be read, write or readwrite.
type Access string

// Permissions is a map of key, a description and an access level.
type Permissions map[string]struct {
Description string `json:"description"`
Access Access `json:"access"`
}

// Route is a struct to serve a folder inside an app
type Route struct {
Folder string `json:"folder"`
Expand Down Expand Up @@ -92,10 +86,10 @@ type Manifest struct {
Description string `json:"description"`
} `json:"locales"`

Version string `json:"version"`
License string `json:"license"`
Permissions *Permissions `json:"permissions"`
Routes Routes `json:"routes"`
Version string `json:"version"`
License string `json:"license"`
Permissions *permissions.Set `json:"permissions"`
Routes Routes `json:"routes"`
}

// ID returns the manifest identifier - see couchdb.Doc interface
Expand Down Expand Up @@ -204,7 +198,7 @@ func (m *Manifest) BuildToken(i *instance.Instance) string {
IssuedAt: crypto.Timestamp(),
Subject: m.Slug,
},
Scope: "io.cozy._all", // TODO scope
Scope: "", // apps token doesnt have a scope
})
if err != nil {
return ""
Expand Down
24 changes: 22 additions & 2 deletions pkg/apps/installer.go
Expand Up @@ -8,6 +8,7 @@ import (
"regexp"

"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/permissions"
"github.com/cozy/cozy-stack/pkg/vfs"
)

Expand Down Expand Up @@ -235,9 +236,28 @@ func (i *Installer) Poll() (man *Manifest, done bool, err error) {
}

func updateManifest(db couchdb.Database, man *Manifest) error {
return couchdb.UpdateDoc(db, man)

err := permissions.Destroy(db, man.Slug)
if err != nil && !couchdb.IsNotFoundError(err) {
return err
}

err = couchdb.UpdateDoc(db, man)
if err != nil {
return err
}

_, err = permissions.Create(db, man.Slug, *man.Permissions)
return err
}

func createManifest(db couchdb.Database, man *Manifest) error {
return couchdb.CreateNamedDoc(db, man)

if err := couchdb.CreateNamedDoc(db, man); err != nil {
return err
}

_, err := permissions.Create(db, man.Slug, *man.Permissions)
return err

}
13 changes: 13 additions & 0 deletions pkg/apps/installer_test.go
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/cozy/cozy-stack/pkg/config"
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/permissions"
"github.com/cozy/cozy-stack/pkg/vfs"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -370,6 +371,18 @@ func TestMain(m *testing.M) {
}
}

err = couchdb.ResetDB(c, consts.Permissions)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

err = couchdb.DefineIndex(c, consts.Permissions, permissions.Index)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

if err = vfs.CreateRootDirDoc(c); err != nil {
fmt.Println(err)
os.Exit(1)
Expand Down
3 changes: 3 additions & 0 deletions pkg/consts/consts.go
Expand Up @@ -19,6 +19,9 @@ const (
// Sessions doc type for sessions identifying a connection
Sessions = "io.cozy.sessions"

// Permissions doc type for permissions identifying a connection
Permissions = "io.cozy.permissions"

// OAuthClients doc type for OAuth2 clients
OAuthClients = "io.cozy.oauth.clients"
// OAuthAccessCodes doc type for OAuth2 access codes
Expand Down
17 changes: 17 additions & 0 deletions pkg/instance/instance.go
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/cozy/cozy-stack/pkg/couchdb/mango"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/cozy/cozy-stack/pkg/jobs"
"github.com/cozy/cozy-stack/pkg/permissions"
"github.com/cozy/cozy-stack/pkg/settings"
"github.com/cozy/cozy-stack/pkg/vfs"
"github.com/cozy/cozy-stack/web/jsonapi"
Expand Down Expand Up @@ -279,6 +280,17 @@ func (i *Instance) createSettings() error {
return settings.CreateDefaultTheme(i)
}

func (i *Instance) createPermissionsDB() error {
err := couchdb.CreateDB(i, consts.Permissions)

if err != nil {
return err
}

return couchdb.DefineIndex(i, consts.Permissions, permissions.Index)

}

// Create builds an instance and initializes it
func Create(opts *Options) (*Instance, error) {
domain := opts.Domain
Expand Down Expand Up @@ -342,6 +354,11 @@ func Create(opts *Options) (*Instance, error) {
return nil, err
}

err = i.createPermissionsDB()
if err != nil {
return nil, err
}

doc := &instanceSettings{
Timezone: opts.Timezone,
Email: opts.Email,
Expand Down
5 changes: 0 additions & 5 deletions pkg/permissions/claims.go
Expand Up @@ -22,8 +22,3 @@ type Claims struct {
jwt.StandardClaims
Scope string `json:"scope,omitempty"`
}

// PermissionsSet returns a set of Permissions parsed from this claims scope
func (c *Claims) PermissionsSet() (Set, error) {
return UnmarshalScopeString(c.Scope)
}
120 changes: 78 additions & 42 deletions pkg/permissions/permissions.go
@@ -1,57 +1,93 @@
package permissions

// Validable is an interface for a object than can be validated by a Set
type Validable interface {
ID() string
DocType() string
Valid(field, expected string) bool
import (
"fmt"

"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/couchdb/mango"
"github.com/cozy/cozy-stack/web/jsonapi"
)

// Permission is a storable object containing a set of rules and
// several codes
type Permission struct {
PID string `json:"_id,omitempty"`
PRev string `json:"_rev,omitempty"`
ApplicationID string `json:"application_id"`
Permissions Set `json:"permissions,omitempty"`
ExpiresAt int `json:"expires_at,omitempty"`
Codes map[string]string `json:"codes,omitempty"`
}

func validValues(r Rule, o Validable) bool {
// empty r.Values = any value
if len(r.Values) == 0 {
return true
}
// Index is the necessary index for this package
// used in instance creation
var Index = mango.IndexOnFields("application_id")

if r.Selector == "" {
return r.ValuesContain(o.ID())
}
// ID implements jsonapi.Doc
func (p *Permission) ID() string { return p.PID }

return r.SomeValue(func(value string) bool {
return o.Valid(r.Selector, value)
})
}
// Rev implements jsonapi.Doc
func (p *Permission) Rev() string { return p.PRev }

func validVerbAndType(r Rule, v Verb, doctype string) bool {
return r.Verbs.Contains(v) && r.Type == doctype
}
// DocType implements jsonapi.Doc
func (p *Permission) DocType() string { return consts.Permissions }

func validWholeType(r Rule) bool {
return len(r.Values) == 0
}
// SetID implements jsonapi.Doc
func (p *Permission) SetID(id string) { p.PID = id }

func validID(r Rule, id string) bool {
return r.Selector == "" && r.ValuesContain(id)
}
// SetRev implements jsonapi.Doc
func (p *Permission) SetRev(rev string) { p.PRev = rev }

// Relationships implements jsonapi.Doc
func (p *Permission) Relationships() jsonapi.RelationshipMap { return nil }

// Included implements jsonapi.Doc
func (p *Permission) Included() []jsonapi.Object { return nil }

// AllowWholeType returns true if the set allows to apply verb to every
// document from the given doctypes (ie. r.values == 0)
func (s Set) AllowWholeType(v Verb, doctype string) bool {
return s.Some(func(r Rule) bool {
return validVerbAndType(r, v, doctype) && validWholeType(r)
})
// SelfLink implements jsonapi.Doc
func (p *Permission) SelfLink() string { return "/permissions/" + p.PID }

// GetForApp retrieves the Permission doc for a given app
func GetForApp(db couchdb.Database, slug string) (*Permission, error) {
var res []Permission
err := couchdb.FindDocs(db, consts.Permissions, &couchdb.FindRequest{
Selector: mango.Equal("application_id", consts.Manifests+"/"+slug),
}, &res)
if err != nil {
return nil, err
}
if len(res) == 0 {
return nil, fmt.Errorf("no permission doc for %v", slug)
}
return &res[0], nil
}

// AllowID returns true if the set allows to apply verb to given type & id
func (s Set) AllowID(v Verb, doctype, id string) bool {
return s.Some(func(r Rule) bool {
return validVerbAndType(r, v, doctype) && (validWholeType(r) || validID(r, id))
})
// Create creates a Permission doc for a given app
func Create(db couchdb.Database, slug string, set Set) (*Permission, error) {
existing, _ := GetForApp(db, slug)
if existing != nil {
return nil, fmt.Errorf("There is already a permission doc for %v", slug)
}

doc := &Permission{
ApplicationID: consts.Manifests + "/" + slug,
Permissions: set, // @TODO some validation?
}

err := couchdb.CreateDoc(db, doc)
if err != nil {
return nil, err
}

return doc, nil
}

// Allow returns true if the set allows to apply verb to given doc
func (s Set) Allow(v Verb, o Validable) bool {
return s.Some(func(r Rule) bool {
return validVerbAndType(r, v, o.DocType()) && validValues(r, o)
})
// Destroy removes Permission doc for a given app
func Destroy(db couchdb.Database, slug string) error {
existing, err := GetForApp(db, slug)
if err != nil {
return err
}
return couchdb.DeleteDoc(db, existing)
}
15 changes: 8 additions & 7 deletions pkg/permissions/permissions_test.go
Expand Up @@ -141,15 +141,16 @@ func TestStringToSet(t *testing.T) {
assert.Error(t, err)

s, err := UnmarshalScopeString("io.cozy.contacts io.cozy.files:GET:io.cozy.files.music-dir")
set := *s

assert.NoError(t, err)
assert.Len(t, s, 2)
assert.Equal(t, "io.cozy.contacts", s[0].Type)
assert.Equal(t, "io.cozy.files", s[1].Type)
assert.Len(t, s[1].Verbs, 1)
assert.Equal(t, Verbs(GET), s[1].Verbs)
assert.Len(t, s[1].Values, 1)
assert.Equal(t, "io.cozy.files.music-dir", s[1].Values[0])
assert.Len(t, set, 2)
assert.Equal(t, "io.cozy.contacts", set[0].Type)
assert.Equal(t, "io.cozy.files", set[1].Type)
assert.Len(t, set[1].Verbs, 1)
assert.Equal(t, Verbs(GET), set[1].Verbs)
assert.Len(t, set[1].Values, 1)
assert.Equal(t, "io.cozy.files.music-dir", set[1].Values[0])

rule, err := UnmarshalRuleString("io.cozy.events:GET:mygreatcalendar,othercalendar:calendar-id")
assert.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions pkg/permissions/set.go
Expand Up @@ -72,7 +72,7 @@ func (ps *Set) UnmarshalJSON(j []byte) error {
}

// UnmarshalScopeString parse a Scope string into a permission Set
func UnmarshalScopeString(in string) (Set, error) {
func UnmarshalScopeString(in string) (*Set, error) {
parts := strings.Split(in, ruleSep)
out := make(Set, len(parts))

Expand All @@ -88,7 +88,7 @@ func UnmarshalScopeString(in string) (Set, error) {
out[i] = s
}

return out, nil
return &out, nil
}

// Some returns true if the predicate return true for any of the rule.
Expand Down
57 changes: 57 additions & 0 deletions pkg/permissions/validation.go
@@ -0,0 +1,57 @@
package permissions

// Validable is an interface for a object than can be validated by a Set
type Validable interface {
ID() string
DocType() string
Valid(field, expected string) bool
}

func validValues(r Rule, o Validable) bool {
// empty r.Values = any value
if len(r.Values) == 0 {
return true
}

if r.Selector == "" {
return r.ValuesContain(o.ID())
}

return r.SomeValue(func(value string) bool {
return o.Valid(r.Selector, value)
})
}

func validVerbAndType(r Rule, v Verb, doctype string) bool {
return r.Verbs.Contains(v) && r.Type == doctype
}

func validWholeType(r Rule) bool {
return len(r.Values) == 0
}

func validID(r Rule, id string) bool {
return r.Selector == "" && r.ValuesContain(id)
}

// AllowWholeType returns true if the set allows to apply verb to every
// document from the given doctypes (ie. r.values == 0)
func (s Set) AllowWholeType(v Verb, doctype string) bool {
return s.Some(func(r Rule) bool {
return validVerbAndType(r, v, doctype) && validWholeType(r)
})
}

// AllowID returns true if the set allows to apply verb to given type & id
func (s Set) AllowID(v Verb, doctype, id string) bool {
return s.Some(func(r Rule) bool {
return validVerbAndType(r, v, doctype) && (validWholeType(r) || validID(r, id))
})
}

// Allow returns true if the set allows to apply verb to given doc
func (s Set) Allow(v Verb, o Validable) bool {
return s.Some(func(r Rule) bool {
return validVerbAndType(r, v, o.DocType()) && validValues(r, o)
})
}

0 comments on commit 606a0e8

Please sign in to comment.