From 9723b648275786b75c44618533e700c796343cc6 Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Sun, 21 Jul 2019 08:41:30 +0300 Subject: [PATCH] Permissions api (#1524) * Add permissions api * Integrate permissions service * Reduce cyclomatic complexity of the MakeNode function --- api/backend.go | 22 ++- node/node.go | 44 ++++-- node/status_node.go | 12 ++ params/config.go | 8 ++ services/permissions/README.md | 38 +++++ services/permissions/api.go | 41 ++++++ services/permissions/api_test.go | 96 +++++++++++++ services/permissions/database.go | 135 ++++++++++++++++++ services/permissions/migrations/bindata.go | 127 ++++++++++++++++ services/permissions/migrations/migrate.go | 18 +++ .../migrations/sql/0001_permissions.down.sql | 2 + .../migrations/sql/0001_permissions.up.sql | 9 ++ services/permissions/migrations/sql/doc.go | 3 + services/permissions/service.go | 62 ++++++++ 14 files changed, 606 insertions(+), 11 deletions(-) create mode 100644 services/permissions/README.md create mode 100644 services/permissions/api.go create mode 100644 services/permissions/api_test.go create mode 100644 services/permissions/database.go create mode 100644 services/permissions/migrations/bindata.go create mode 100644 services/permissions/migrations/migrate.go create mode 100644 services/permissions/migrations/sql/0001_permissions.down.sql create mode 100644 services/permissions/migrations/sql/0001_permissions.up.sql create mode 100644 services/permissions/migrations/sql/doc.go create mode 100644 services/permissions/service.go diff --git a/api/backend.go b/api/backend.go index 441c08a11..76102740c 100644 --- a/api/backend.go +++ b/api/backend.go @@ -571,7 +571,11 @@ func (b *StatusBackend) SelectAccount(walletAddress, chatAddress, password strin if err != nil { return err } - return b.startBrowsers(password) + err = b.startBrowsers(password) + if err != nil { + return err + } + return b.startPermissions(password) } func (b *StatusBackend) startWallet(password string) error { @@ -609,6 +613,22 @@ func (b *StatusBackend) startBrowsers(password string) error { return svc.StartDatabase(path, password) } +func (b *StatusBackend) startPermissions(password string) error { + if !b.statusNode.Config().PermissionsConfig.Enabled { + return nil + } + svc, err := b.statusNode.PermissionsService() + if err != nil { + return err + } + account, err := b.accountManager.SelectedWalletAccount() + if err != nil { + return err + } + path := path.Join(b.statusNode.Config().DataDir, fmt.Sprintf("permissions-%x.sql", account.Address)) + return svc.StartDatabase(path, password) +} + // SendDataNotification sends data push notifications to users. // dataPayloadJSON is a JSON string that looks like this: // { diff --git a/node/node.go b/node/node.go index a6b121765..580898ed9 100644 --- a/node/node.go +++ b/node/node.go @@ -27,6 +27,7 @@ import ( "github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/incentivisation" "github.com/status-im/status-go/services/peer" + "github.com/status-im/status-go/services/permissions" "github.com/status-im/status-go/services/personal" "github.com/status-im/status-go/services/shhext" "github.com/status-im/status-go/services/status" @@ -49,6 +50,7 @@ var ( ErrIncentivisationServiceRegistrationFailure = errors.New("failed to register the Incentivisation service") ErrWalletServiceRegistrationFailure = errors.New("failed to register the Wallet service") ErrBrowsersServiceRegistrationFailure = errors.New("failed to register the Browsers service") + ErrPermissionsServiceRegistrationFailure = errors.New("failed to register the Permissions service") ) // All general log messages in this package should be routed through this logger. @@ -80,14 +82,22 @@ func MakeNode(config *params.NodeConfig, db *leveldb.DB) (*node.Node, error) { return nil, fmt.Errorf(ErrNodeMakeFailureFormat, err.Error()) } + err = activateServices(stack, config, db) + if err != nil { + return nil, err + } + return stack, nil +} + +func activateServices(stack *node.Node, config *params.NodeConfig, db *leveldb.DB) error { // start Ethereum service if we are not expected to use an upstream server if !config.UpstreamConfig.Enabled { if err := activateLightEthService(stack, config); err != nil { - return nil, fmt.Errorf("%v: %v", ErrLightEthRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrLightEthRegistrationFailure, err) } } else { if config.LightEthConfig.Enabled { - return nil, fmt.Errorf("%v: %v", ErrLightEthRegistrationFailureUpstreamEnabled, err) + return ErrLightEthRegistrationFailureUpstreamEnabled } logger.Info("LES protocol is disabled") @@ -98,39 +108,43 @@ func MakeNode(config *params.NodeConfig, db *leveldb.DB) (*node.Node, error) { // upstream, we don't start any of these, so we need to start our own // implementation. if err := activatePersonalService(stack, config); err != nil { - return nil, fmt.Errorf("%v: %v", ErrPersonalServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrPersonalServiceRegistrationFailure, err) } } // start Whisper service. if err := activateShhService(stack, config, db); err != nil { - return nil, fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err) } // start incentivisation service if err := activateIncentivisationService(stack, config); err != nil { - return nil, fmt.Errorf("%v: %v", ErrIncentivisationServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrIncentivisationServiceRegistrationFailure, err) } // start status service. if err := activateStatusService(stack, config); err != nil { - return nil, fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err) } // start peer service if err := activatePeerService(stack); err != nil { - return nil, fmt.Errorf("%v: %v", ErrPeerServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrPeerServiceRegistrationFailure, err) } if err := activateWalletService(stack, config.WalletConfig); err != nil { - return nil, fmt.Errorf("%v: %v", ErrWalletServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrWalletServiceRegistrationFailure, err) } if err := activateBrowsersService(stack, config.BrowsersConfig); err != nil { - return nil, fmt.Errorf("%v: %v", ErrBrowsersServiceRegistrationFailure, err) + return fmt.Errorf("%v: %v", ErrBrowsersServiceRegistrationFailure, err) } - return stack, nil + if err := activatePermissionsService(stack, config.PermissionsConfig); err != nil { + return fmt.Errorf("%v: %v", ErrPermissionsServiceRegistrationFailure, err) + } + + return nil } // newGethNodeConfig returns default stack configuration for mobile client node @@ -301,6 +315,16 @@ func activateBrowsersService(stack *node.Node, config params.BrowsersConfig) err }) } +func activatePermissionsService(stack *node.Node, config params.PermissionsConfig) error { + if !config.Enabled { + logger.Info("dapps permissions service is disabled") + return nil + } + return stack.Register(func(*node.ServiceContext) (node.Service, error) { + return permissions.NewService(), nil + }) +} + func registerMailServer(whisperService *whisper.Whisper, config *params.WhisperConfig) (err error) { var mailServer mailserver.WMailServer whisperService.RegisterServer(&mailServer) diff --git a/node/status_node.go b/node/status_node.go index 00f641be2..23bc5358a 100644 --- a/node/status_node.go +++ b/node/status_node.go @@ -31,6 +31,7 @@ import ( "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/peer" + "github.com/status-im/status-go/services/permissions" "github.com/status-im/status-go/services/shhext" "github.com/status-im/status-go/services/status" "github.com/status-im/status-go/services/wallet" @@ -614,6 +615,17 @@ func (n *StatusNode) BrowsersService() (s *browsers.Service, err error) { return } +// PermissionsService returns browsers.Service instance if it was started. +func (n *StatusNode) PermissionsService() (s *permissions.Service, err error) { + n.mu.RLock() + defer n.mu.RUnlock() + err = n.gethService(&s) + if err == node.ErrServiceUnknown { + err = ErrServiceUnknown + } + return +} + // AccountManager exposes reference to node's accounts manager func (n *StatusNode) AccountManager() (*accounts.Manager, error) { n.mu.RLock() diff --git a/params/config.go b/params/config.go index 4491f332d..33567acd7 100644 --- a/params/config.go +++ b/params/config.go @@ -349,6 +349,9 @@ type NodeConfig struct { // BrowsersConfig extra configuration for browsers.Service. BrowsersConfig BrowsersConfig + // PermissionsConfig extra configuration for permissions.Service. + PermissionsConfig PermissionsConfig + // SwarmConfig extra configuration for Swarm and ENS SwarmConfig SwarmConfig `json:"SwarmConfig," validate:"structonly"` @@ -374,6 +377,11 @@ type BrowsersConfig struct { Enabled bool } +// PermissionsConfig extra configuration for permissions.Service. +type PermissionsConfig struct { + Enabled bool +} + // ShhextConfig defines options used by shhext service. type ShhextConfig struct { PFSEnabled bool diff --git a/services/permissions/README.md b/services/permissions/README.md new file mode 100644 index 000000000..1133b9edc --- /dev/null +++ b/services/permissions/README.md @@ -0,0 +1,38 @@ +Dapps permissions service +========================= + +To enable: + +```json +{ + "PermissionsConfig": { + "Enabled": true, + }, + APIModules: "permissions" +} +``` + +API +--- + +#### permissions_addDappPermissions + +Stores provided permissions for dapp. On update replaces previous version of the object. + +```json +{ + "dapp": "first", + "permissions": [ + "r", + "x" + ] +} +``` + +#### permissions_getDappPermissions + +Returns all permissions for dapps. Order is not deterministic. + +#### permissions_deleteDappPermissions + +Delete dapp by a name. \ No newline at end of file diff --git a/services/permissions/api.go b/services/permissions/api.go new file mode 100644 index 000000000..117f4e655 --- /dev/null +++ b/services/permissions/api.go @@ -0,0 +1,41 @@ +package permissions + +import ( + "context" + "errors" +) + +var ( + // ErrServiceNotInitialized returned when permissions is not initialized/started,. + ErrServiceNotInitialized = errors.New("permissions service is not initialized") +) + +func NewAPI(s *Service) *API { + return &API{s} +} + +// API is class with methods available over RPC. +type API struct { + s *Service +} + +func (api *API) AddDappPermissions(ctx context.Context, perms DappPermissions) error { + if api.s.db == nil { + return ErrServiceNotInitialized + } + return api.s.db.AddPermissions(perms) +} + +func (api *API) GetDappPermissions(ctx context.Context) ([]DappPermissions, error) { + if api.s.db == nil { + return nil, ErrServiceNotInitialized + } + return api.s.db.GetPermissions() +} + +func (api *API) DeleteDappPermissions(ctx context.Context, name string) error { + if api.s.db == nil { + return ErrServiceNotInitialized + } + return api.s.db.DeletePermission(name) +} diff --git a/services/permissions/api_test.go b/services/permissions/api_test.go new file mode 100644 index 000000000..836a62e20 --- /dev/null +++ b/services/permissions/api_test.go @@ -0,0 +1,96 @@ +package permissions + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "sort" + "testing" + + "github.com/stretchr/testify/require" +) + +func setupTestDB(t *testing.T) (*Database, func()) { + tmpfile, err := ioutil.TempFile("", "perm-tests-") + require.NoError(t, err) + db, err := InitializeDB(tmpfile.Name(), "perm-tests") + require.NoError(t, err) + return db, func() { + require.NoError(t, db.Close()) + require.NoError(t, os.Remove(tmpfile.Name())) + } +} + +func setupTestAPI(t *testing.T) (*API, func()) { + db, cancel := setupTestDB(t) + return &API{s: &Service{db: db}}, cancel +} + +func TestDappPermissionsStored(t *testing.T) { + api, cancel := setupTestAPI(t) + defer cancel() + + expected := []DappPermissions{ + { + Name: "first", + Permissions: []string{"r", "w"}, + }, + { + Name: "second", + Permissions: []string{"r", "x"}, + }, + { + Name: "third", + }, + } + for _, perms := range expected { + require.NoError(t, api.AddDappPermissions(context.TODO(), perms)) + } + rst, err := api.GetDappPermissions(context.TODO()) + require.NoError(t, err) + // sort in lexicographic order by name + sort.Slice(rst, func(i, j int) bool { + return rst[i].Name < rst[j].Name + }) + require.Equal(t, expected, rst) + + data, err := json.Marshal(rst) + require.NoError(t, err) + fmt.Println(string(data)) +} + +func TestDappPermissionsReplacedOnUpdated(t *testing.T) { + api, cancel := setupTestAPI(t) + defer cancel() + + perms := DappPermissions{ + Name: "first", + Permissions: []string{"r", "w"}, + } + require.NoError(t, api.AddDappPermissions(context.TODO(), perms)) + perms.Permissions = append(perms.Permissions, "x") + require.NoError(t, api.AddDappPermissions(context.TODO(), perms)) + rst, err := api.GetDappPermissions(context.TODO()) + require.NoError(t, err) + require.Len(t, rst, 1) + require.Equal(t, perms, rst[0]) +} + +func TestDappPermissionsDeleted(t *testing.T) { + api, cancel := setupTestAPI(t) + defer cancel() + + perms := DappPermissions{ + Name: "first", + } + require.NoError(t, api.AddDappPermissions(context.TODO(), perms)) + rst, err := api.GetDappPermissions(context.TODO()) + require.NoError(t, err) + require.Len(t, rst, 1) + require.NoError(t, api.DeleteDappPermissions(context.TODO(), perms.Name)) + rst, err = api.GetDappPermissions(context.TODO()) + require.NoError(t, err) + require.Len(t, rst, 0) +} diff --git a/services/permissions/database.go b/services/permissions/database.go new file mode 100644 index 000000000..c5f4a208d --- /dev/null +++ b/services/permissions/database.go @@ -0,0 +1,135 @@ +package permissions + +import ( + "database/sql" + + "github.com/status-im/status-go/services/permissions/migrations" + "github.com/status-im/status-go/sqlite" +) + +// Database sql wrapper for operations with browser objects. +type Database struct { + db *sql.DB +} + +// Close closes database. +func (db Database) Close() error { + return db.db.Close() +} + +// InitializeDB creates db file at a given path and applies migrations. +func InitializeDB(path, password string) (*Database, error) { + db, err := sqlite.OpenDB(path, password) + if err != nil { + return nil, err + } + err = migrations.Migrate(db) + if err != nil { + return nil, err + } + return &Database{db: db}, nil +} + +type DappPermissions struct { + Name string `json:"dapp"` + Permissions []string `json:"permissions,omitempty"` +} + +func (db *Database) AddPermissions(perms DappPermissions) (err error) { + var ( + tx *sql.Tx + insert *sql.Stmt + ) + tx, err = db.db.Begin() + if err != nil { + return + } + defer func() { + if err == nil { + err = tx.Commit() + return + } + _ = tx.Rollback() + }() + insert, err = tx.Prepare("INSERT OR REPLACE INTO dapps(name) VALUES(?)") + if err != nil { + return + } + _, err = insert.Exec(perms.Name) + insert.Close() + if err != nil { + return + } + if len(perms.Permissions) == 0 { + return + } + insert, err = tx.Prepare("INSERT INTO permissions(dapp_name, permission) VALUES(?, ?)") + if err != nil { + return + } + defer insert.Close() + for _, perm := range perms.Permissions { + _, err = insert.Exec(perms.Name, perm) + if err != nil { + return + } + } + return +} + +func (db *Database) GetPermissions() (rst []DappPermissions, err error) { + var ( + tx *sql.Tx + rows *sql.Rows + ) + tx, err = db.db.Begin() + if err != nil { + return + } + defer func() { + if err == nil { + err = tx.Commit() + return + } + _ = tx.Rollback() + }() + // FULL and RIGHT joins are not supported + rows, err = tx.Query("SELECT name FROM dapps") + if err != nil { + return + } + dapps := map[string]*DappPermissions{} + for rows.Next() { + perms := DappPermissions{} + err = rows.Scan(&perms.Name) + if err != nil { + return nil, err + } + dapps[perms.Name] = &perms + } + rows, err = tx.Query("SELECT dapp_name, permission from permissions") + if err != nil { + return + } + var ( + name string + permission string + ) + for rows.Next() { + err = rows.Scan(&name, &permission) + if err != nil { + return + } + dapps[name].Permissions = append(dapps[name].Permissions, permission) + } + rst = make([]DappPermissions, 0, len(dapps)) + for key := range dapps { + rst = append(rst, *dapps[key]) + } + return rst, nil +} + +func (db *Database) DeletePermission(name string) error { + _, err := db.db.Exec("DELETE FROM dapps WHERE name = ?", name) + return err +} diff --git a/services/permissions/migrations/bindata.go b/services/permissions/migrations/bindata.go new file mode 100644 index 000000000..bc4039546 --- /dev/null +++ b/services/permissions/migrations/bindata.go @@ -0,0 +1,127 @@ +package migrations + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "strings" +) + +func bindata_read(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + return buf.Bytes(), nil +} + +var __0001_permissions_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\x49\x2c\x28\x28\xb6\xe6\x42\x12\x29\x48\x2d\xca\xcd\x2c\x2e\xce\xcc\xcf\x2b\xb6\xe6\x02\x04\x00\x00\xff\xff\xeb\x21\xe7\xd0\x2a\x00\x00\x00") + +func _0001_permissions_down_sql() ([]byte, error) { + return bindata_read( + __0001_permissions_down_sql, + "0001_permissions.down.sql", + ) +} + +var __0001_permissions_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\xce\x31\x0f\x82\x30\x10\x05\xe0\xbd\xbf\xe2\x8d\x90\xf8\x0f\x9c\x6a\x79\x68\x63\x6d\x4d\x39\x02\x4c\x86\x44\x06\x06\x90\xc8\xff\x4f\x4c\xa3\x91\xc4\xc1\xf5\xee\xdd\x77\xcf\x44\x6a\x21\x44\x1f\x1c\x61\x4b\xf8\x20\x60\x6b\x2b\xa9\x70\xef\x97\x65\x45\xa6\xe6\x7e\x1a\x20\x6c\x05\xd7\x68\x2f\x3a\x76\x38\xb3\x53\x39\x1a\x2b\xa7\x50\x0b\x62\x68\x6c\xb1\x57\xea\x0f\xb5\x0c\xcf\x69\x5c\xd7\xf1\x31\x27\x30\xc1\xb7\x4d\x4d\x39\x5f\x3b\xb7\x53\x5b\xec\x77\x53\x86\x48\x7b\xf4\xe9\x73\xf6\x3d\xcf\x11\x59\x32\xd2\x1b\x7e\xda\x66\xef\x71\xf0\x28\xe8\x28\x84\xd1\x95\xd1\x05\x55\xfe\x0a\x00\x00\xff\xff\x9e\x9a\xc6\xf0\xe8\x00\x00\x00") + +func _0001_permissions_up_sql() ([]byte, error) { + return bindata_read( + __0001_permissions_up_sql, + "0001_permissions.up.sql", + ) +} + +var _doc_go = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00") + +func doc_go() ([]byte, error) { + return bindata_read( + _doc_go, + "doc.go", + ) +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + return f() + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() ([]byte, error){ + "0001_permissions.down.sql": _0001_permissions_down_sql, + "0001_permissions.up.sql": _0001_permissions_up_sql, + "doc.go": doc_go, +} +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for name := range node.Children { + rv = append(rv, name) + } + return rv, nil +} + +type _bintree_t struct { + Func func() ([]byte, error) + Children map[string]*_bintree_t +} +var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ + "0001_permissions.down.sql": &_bintree_t{_0001_permissions_down_sql, map[string]*_bintree_t{ + }}, + "0001_permissions.up.sql": &_bintree_t{_0001_permissions_up_sql, map[string]*_bintree_t{ + }}, + "doc.go": &_bintree_t{doc_go, map[string]*_bintree_t{ + }}, +}} diff --git a/services/permissions/migrations/migrate.go b/services/permissions/migrations/migrate.go new file mode 100644 index 000000000..825e1ddfc --- /dev/null +++ b/services/permissions/migrations/migrate.go @@ -0,0 +1,18 @@ +package migrations + +import ( + "database/sql" + + bindata "github.com/status-im/migrate/v4/source/go_bindata" + "github.com/status-im/status-go/sqlite" +) + +// Migrate applies migrations. +func Migrate(db *sql.DB) error { + return sqlite.Migrate(db, bindata.Resource( + AssetNames(), + func(name string) ([]byte, error) { + return Asset(name) + }, + )) +} diff --git a/services/permissions/migrations/sql/0001_permissions.down.sql b/services/permissions/migrations/sql/0001_permissions.down.sql new file mode 100644 index 000000000..4fd1459ff --- /dev/null +++ b/services/permissions/migrations/sql/0001_permissions.down.sql @@ -0,0 +1,2 @@ +DROP TABLE dapps; +DROP TABLE permissions; diff --git a/services/permissions/migrations/sql/0001_permissions.up.sql b/services/permissions/migrations/sql/0001_permissions.up.sql new file mode 100644 index 000000000..9a7de7533 --- /dev/null +++ b/services/permissions/migrations/sql/0001_permissions.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS dapps ( +name TEXT PRIMARY KEY +) WITHOUT ROWID; + +CREATE TABLE IF NOT EXISTS permissions ( +dapp_name TEXT NOT NULL, +permission TEXT NOT NULL, +FOREIGN KEY(dapp_name) REFERENCES dapps(name) ON DELETE CASCADE +) \ No newline at end of file diff --git a/services/permissions/migrations/sql/doc.go b/services/permissions/migrations/sql/doc.go new file mode 100644 index 000000000..e0a060394 --- /dev/null +++ b/services/permissions/migrations/sql/doc.go @@ -0,0 +1,3 @@ +package sql + +//go:generate go-bindata -pkg migrations -o ../bindata.go ./ diff --git a/services/permissions/service.go b/services/permissions/service.go new file mode 100644 index 000000000..36c279a24 --- /dev/null +++ b/services/permissions/service.go @@ -0,0 +1,62 @@ +package permissions + +import ( + "sync" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +// NewService initializes service instance. +func NewService() *Service { + return &Service{} +} + +type Service struct { + mu sync.Mutex + db *Database +} + +// Start a service. +func (s *Service) Start(*p2p.Server) error { + return nil +} + +// StartDatabase after dbpath and password will become known. +func (s *Service) StartDatabase(dbpath, password string) (err error) { + s.mu.Lock() + defer s.mu.Unlock() + s.db, err = InitializeDB(dbpath, password) + return err +} + +func (s *Service) StopDatabase() error { + s.mu.Lock() + defer s.mu.Unlock() + if s.db != nil { + return s.db.Close() + } + return nil +} + +// Stop a service. +func (s *Service) Stop() error { + return s.StopDatabase() +} + +// APIs returns list of available RPC APIs. +func (s *Service) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "permissions", + Version: "0.1.0", + Service: NewAPI(s), + Public: true, + }, + } +} + +// Protocols returns list of p2p protocols. +func (s *Service) Protocols() []p2p.Protocol { + return nil +}