Skip to content

Commit

Permalink
Merge pull request #592 from fairDataSociety/groups
Browse files Browse the repository at this point in the history
create and open group
  • Loading branch information
asabya authored Feb 14, 2024
2 parents e2fb27b + 21bd5fb commit 7a6c933
Show file tree
Hide file tree
Showing 38 changed files with 5,219 additions and 57 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ The user can share files in his pod with any other user just like in other centr

Pod creation is cheap. A user can create multiple pods and use it to organise his data. for ex: Personal-Pod, Applications-Pod etc.

## (NEW) What is a group?
A group is a shared drive created by a user. It is basically a pod, but on steroids. Group Owner can add members and update permissions. Members with "write" permission can create and store any number of files or directories in a group.

## How to run FairOS-dfs?
Run the following command to download the latest release

Expand Down
10 changes: 9 additions & 1 deletion cmd/dfs-cli/cmd/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,19 @@ func initPrompt() {
prompt.OptionPrefix(currentPrompt),
prompt.OptionLivePrefix(changeLivePrefix),
prompt.OptionTitle("dfs"),
prompt.OptionSetExitCheckerOnInput(exitChecker),
)
p.Run()
}

func exitChecker(in string, breakline bool) bool {
if breakline && strings.TrimSpace(in) == "exit" {
fmt.Println("exiting dfs-cli")
return true
}
return false
}

func changeLivePrefix() (string, bool) {
return currentPrompt, true
}
Expand Down Expand Up @@ -261,7 +270,6 @@ func executor(in string) {
case "help":
help()
case "exit":
os.Exit(0)
case "user":
if len(blocks) < 2 {
log.Println("invalid command.")
Expand Down
15 changes: 15 additions & 0 deletions cmd/dfs/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,21 @@ func startHttpService(logger logging.Logger) *http.Server {
podRouter.HandleFunc("/fork", handler.PodForkHandler).Methods("POST")
podRouter.HandleFunc("/fork-from-reference", handler.PodForkFromReferenceHandler).Methods("POST")

groupRouter := baseRouter.PathPrefix("/group/").Subrouter()
groupRouter.Use(handler.LoginMiddleware)
groupRouter.HandleFunc("/new", handler.GroupCreateHandler).Methods("POST")
groupRouter.HandleFunc("/ls", handler.GroupListHandler).Methods("GET")
groupRouter.HandleFunc("/delete", handler.GroupDeleteHandler).Methods("DELETE")
groupRouter.HandleFunc("/delete-shared", handler.GroupDeleteSharedHandler).Methods("DELETE")
groupRouter.HandleFunc("/accept", handler.GroupAcceptInviteHandler).Methods("POST")
groupRouter.HandleFunc("/invite", handler.GroupAddMemberHandler).Methods("POST")
groupRouter.HandleFunc("/remove", handler.GroupRemoveMemberHandler).Methods("POST")
groupRouter.HandleFunc("/update-permission", handler.GroupUpdatePermissionHandler).Methods("POST")
groupRouter.HandleFunc("/close", handler.GroupCloseHandler).Methods("POST")
groupRouter.HandleFunc("/members", handler.GroupGetMembers).Methods("GET")
groupRouter.HandleFunc("/permission", handler.GroupGetPermission).Methods("GET")
groupRouter.HandleFunc("/open", handler.GroupOpenHandler).Methods("POST")

// directory related handlers
dirRouter := baseRouter.PathPrefix("/dir/").Subrouter()
dirRouter.Use(handler.LoginMiddleware)
Expand Down
117 changes: 117 additions & 0 deletions cmd/dfs/cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import (
"testing"
"time"

"github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl"
"github.com/stretchr/testify/assert"

mockpost "github.com/ethersphere/bee/pkg/postage/mock"
mockstorer "github.com/ethersphere/bee/pkg/storer/mock"
"github.com/fairdatasociety/fairOS-dfs/cmd/common"
Expand Down Expand Up @@ -1081,6 +1084,120 @@ func TestApis(t *testing.T) {
}
})

t.Run("group-test", func(t *testing.T) {
c := http.Client{Timeout: time.Duration(1) * time.Minute}
userRequest := &common.UserSignupRequest{
UserName: randStringRunes(16),
Password: randStringRunes(12),
}

userBytes, err := json.Marshal(userRequest)
if err != nil {
t.Fatal(err)
}

signupRequestDataHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev2, string(common.UserSignup)), bytes.NewBuffer(userBytes))
if err != nil {
t.Fatal(err)
}
signupRequestDataHttpReq.Header.Add("Content-Type", "application/json")
signupRequestDataHttpReq.Header.Add("Content-Length", strconv.Itoa(len(userBytes)))
signupRequestResp, err := c.Do(signupRequestDataHttpReq)
if err != nil {
t.Fatal(err)
}

err = signupRequestResp.Body.Close()
if err != nil {
t.Fatal(err)
}
if signupRequestResp.StatusCode != http.StatusCreated {
t.Fatal("Signup failed", signupRequestResp.StatusCode)
}

userLoginHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev2, string(common.UserLogin)), bytes.NewBuffer(userBytes))
if err != nil {
t.Fatal(err)

}
userLoginHttpReq.Header.Add("Content-Type", "application/json")
userLoginHttpReq.Header.Add("Content-Length", strconv.Itoa(len(userBytes)))
userLoginResp, err := c.Do(userLoginHttpReq)
if err != nil {
t.Fatal(err)
}
err = userLoginResp.Body.Close()
if err != nil {
t.Fatal(err)
}
if userLoginResp.StatusCode != http.StatusOK {
t.Fatal("user should be able to login")
}
cookie := userLoginResp.Header["Set-Cookie"]
groupRequest := &api.GroupNameRequest{
GroupName: randStringRunes(16),
}

groupBytes, err := json.Marshal(groupRequest)
if err != nil {
t.Fatal(err)
}

groupNewHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev1, "/group/new"), bytes.NewBuffer(groupBytes))
if err != nil {
t.Fatal(err)
}
groupNewHttpReq.Header.Set("Cookie", cookie[0])
groupNewHttpReq.Header.Add("Content-Type", "application/json")
groupNewHttpReq.Header.Add("Content-Length", strconv.Itoa(len(groupBytes)))
groupNewResp, err := c.Do(groupNewHttpReq)
if err != nil {
t.Fatal(err)
}

err = groupNewResp.Body.Close()
if err != nil {
t.Fatal(err)
}
if groupNewResp.StatusCode != 201 {
t.Fatal("group creation failed")
}

// check for own permission
groupPermHttpReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s%s?groupName=%s", basev1, "/group/permission", groupRequest.GroupName), nil)
if err != nil {
t.Fatal(err)
}
groupPermHttpReq.Header.Set("Cookie", cookie[0])
groupPermHttpReq.Header.Add("Content-Type", "application/json")
groupPermHttpReq.Header.Add("Content-Length", strconv.Itoa(len(groupBytes)))
groupPermResp, err := c.Do(groupPermHttpReq)
if err != nil {
t.Fatal(err)
}

permResp, err := io.ReadAll(groupPermResp.Body)
if err != nil {
t.Fatal(err)
}
perm := &api.GroupPermissionResponse{}
err = json.Unmarshal(permResp, &perm)
if err != nil {
t.Fatal(err)
}
err = groupPermResp.Body.Close()
if err != nil {
t.Fatal(err)
}
if groupPermResp.StatusCode != 200 {
t.Fatal("group permission failed")
}

if !assert.Equal(t, perm.Permission, acl.PermissionWrite) {
t.Fatal("permission should be write")
}
})

t.Run("ws test", func(t *testing.T) {
u := url.URL{Scheme: "ws", Host: base, Path: "/ws/v1/"}
header := http.Header{}
Expand Down
11 changes: 11 additions & 0 deletions pkg/acl/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package acl

type ACL interface {
CreateGroup(groupName, ownerAddress string) error
AddMember(groupName, ownerAddress, memberAddress string, permission uint8) error
RemoveMember(groupName, ownerAddress, memberAddress string) error
RemoveGroup(groupName, ownerAddress string) error
GetGroupMembers(groupName, ownerAddress string) (map[string]uint8, error)
UpdatePermission(groupName, ownerAddress, memberAddress string, permission uint8) error
GetPermission(groupName, ownerAddress, memberAddress string) (uint8, error)
}
141 changes: 141 additions & 0 deletions pkg/acl/acl/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package acl

import (
"bytes"
"encoding/json"
"io"
"sync"

"github.com/fairdatasociety/fairOS-dfs/pkg/blockstore"
"github.com/fairdatasociety/fairOS-dfs/pkg/feed"
f "github.com/fairdatasociety/fairOS-dfs/pkg/file"
"github.com/fairdatasociety/fairOS-dfs/pkg/logging"
"github.com/fairdatasociety/fairOS-dfs/pkg/utils"
)

const (
NoPermission uint8 = 0
PermissionRead uint8 = 1
PermissionWrite uint8 = 2
PermissionExecute uint8 = 4
)

type ACL struct {
c blockstore.Client
logger logging.Logger
f *feed.API
lock sync.Mutex
listMap map[string]map[string]uint8
}

func (a *ACL) GetGroupMembers(groupName, ownerAddress string) (map[string]uint8, error) {
err := a.loadPermissions(groupName, ownerAddress)
if err != nil {
return nil, err
}
a.lock.Lock()
defer a.lock.Unlock()
return a.listMap[groupName], nil
}

func (a *ACL) UpdatePermission(groupName, ownerAddress, memberAddress string, permission uint8) error {
a.lock.Lock()
a.listMap[groupName][memberAddress] = permission
a.lock.Unlock()

return a.storePermissions(groupName, ownerAddress)
}

func (a *ACL) GetPermission(groupName, ownerAddress, memberAddress string) (uint8, error) {
err := a.loadPermissions(groupName, ownerAddress)
if err != nil {
return 0, err
}
a.lock.Lock()
defer a.lock.Unlock()

return a.listMap[groupName][memberAddress], nil
}

func (a *ACL) CreateGroup(groupName, ownerAddress string) error {
return a.storePermissions(groupName, ownerAddress)
}

func (a *ACL) AddMember(groupName, ownerAddress, memberAddress string, permission uint8) error {
a.lock.Lock()
a.listMap[groupName][memberAddress] = permission
a.lock.Unlock()
return a.storePermissions(groupName, ownerAddress)
}

func (a *ACL) RemoveMember(groupName, ownerAddress, memberAddress string) error {
a.lock.Lock()
delete(a.listMap[groupName], memberAddress)
a.lock.Unlock()
return a.storePermissions(groupName, ownerAddress)
}

func (a *ACL) RemoveGroup(groupName, ownerAddress string) error {
a.lock.Lock()
delete(a.listMap, groupName)
a.lock.Unlock()
return a.storePermissions(groupName, ownerAddress)
}

func NewACL(c blockstore.Client, f *feed.API, logger logging.Logger) *ACL {
return &ACL{
c: c,
f: f,
logger: logger,
listMap: map[string]map[string]uint8{},
}
}

func (a *ACL) loadPermissions(group, ownerAddress string) error {
f2 := f.NewFile("", a.c, a.f, utils.HexToAddress(ownerAddress), nil, a.logger)
topicString := utils.CombinePathAndFile(ownerAddress, group)
r, _, err := f2.Download(topicString, "")
if err != nil { // skipcq: TCV-001
return err
}
permissions := map[string]uint8{}
data, err := io.ReadAll(r)
if err != nil { // skipcq: TCV-001
return err
}

if len(data) == 0 {
a.listMap[group] = permissions
return nil
}

err = json.Unmarshal(data, &permissions)
if err != nil { // skipcq: TCV-001
return err
}

a.lock.Lock()
defer a.lock.Unlock()
a.listMap[group] = permissions
return nil
}

func (a *ACL) storePermissions(group, ownerAddress string) error {
a.lock.Lock()
defer a.lock.Unlock()
permissions := map[string]uint8{}
if _, ok := a.listMap[group]; ok {
permissions = a.listMap[group]
} else {
permissions[ownerAddress] = PermissionWrite
a.listMap[group] = permissions
}
data, err := json.Marshal(&permissions)
if err != nil {
return err
}

f2 := f.NewFile("", a.c, a.f, utils.HexToAddress(ownerAddress), nil, a.logger)
topicString := utils.CombinePathAndFile(ownerAddress, group)
return f2.Upload(bytes.NewReader(data), topicString, int64(len(data)), f.MinBlockSize, 0, "/", "gzip", "")
}
Loading

0 comments on commit 7a6c933

Please sign in to comment.