diff --git a/changelog/unreleased/ocm-invite-list.md b/changelog/unreleased/ocm-invite-list.md new file mode 100644 index 0000000000..951508074e --- /dev/null +++ b/changelog/unreleased/ocm-invite-list.md @@ -0,0 +1,7 @@ +Enhancement: List valid OCM invite tokens + +Adds the endpoint `/list-invite` in the sciencemesh service, +to get the list of valid OCM invite tokens. + +https://github.com/cs3org/reva/pull/3665 +https://github.com/cs3org/cs3apis/pull/201 \ No newline at end of file diff --git a/go.mod b/go.mod index 4dbdff2219..2588c2d0e1 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20230214162720-ac2ceb2ad50e + github.com/cs3org/go-cs3apis v0.0.0-20230220105024-9b045290a172 github.com/dgraph-io/ristretto v0.1.1 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 diff --git a/go.sum b/go.sum index 8c11b51d7c..b84aca5db9 100644 --- a/go.sum +++ b/go.sum @@ -286,6 +286,8 @@ github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJff github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20230214162720-ac2ceb2ad50e h1:w4A601AS6pC+3eHb9XDZe5Ctpi4cFyoFnsed2Yisd8Q= github.com/cs3org/go-cs3apis v0.0.0-20230214162720-ac2ceb2ad50e/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20230220105024-9b045290a172 h1:S2WbxNSNhrrQGIlvfNkMAmekcQtINvmU51gsKFZEKPo= +github.com/cs3org/go-cs3apis v0.0.0-20230220105024-9b045290a172/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/internal/grpc/services/gateway/ocminvitemanager.go b/internal/grpc/services/gateway/ocminvitemanager.go index 4e0160f342..9bf4de3017 100644 --- a/internal/grpc/services/gateway/ocminvitemanager.go +++ b/internal/grpc/services/gateway/ocminvitemanager.go @@ -43,6 +43,22 @@ func (s *svc) GenerateInviteToken(ctx context.Context, req *invitepb.GenerateInv return res, nil } +func (s *svc) ListInviteTokens(ctx context.Context, req *invitepb.ListInviteTokensRequest) (*invitepb.ListInviteTokensResponse, error) { + c, err := pool.GetOCMInviteManagerClient(pool.Endpoint(s.c.OCMInviteManagerEndpoint)) + if err != nil { + return &invitepb.ListInviteTokensResponse{ + Status: status.NewInternal(ctx, err, "error getting user invite provider client"), + }, nil + } + + res, err := c.ListInviteTokens(ctx, req) + if err != nil { + return nil, errors.Wrap(err, "gateway: error calling ListInviteTokens") + } + + return res, nil +} + func (s *svc) ForwardInvite(ctx context.Context, req *invitepb.ForwardInviteRequest) (*invitepb.ForwardInviteResponse, error) { c, err := pool.GetOCMInviteManagerClient(pool.Endpoint(s.c.OCMInviteManagerEndpoint)) if err != nil { diff --git a/internal/grpc/services/ocminvitemanager/ocminvitemanager.go b/internal/grpc/services/ocminvitemanager/ocminvitemanager.go index 648664f4ae..508cac88af 100644 --- a/internal/grpc/services/ocminvitemanager/ocminvitemanager.go +++ b/internal/grpc/services/ocminvitemanager/ocminvitemanager.go @@ -150,6 +150,20 @@ func (s *service) GenerateInviteToken(ctx context.Context, req *invitepb.Generat }, nil } +func (s *service) ListInviteTokens(ctx context.Context, req *invitepb.ListInviteTokensRequest) (*invitepb.ListInviteTokensResponse, error) { + user := ctxpkg.ContextMustGetUser(ctx) + tokens, err := s.repo.ListTokens(ctx, user.Id) + if err != nil { + return &invitepb.ListInviteTokensResponse{ + Status: status.NewInternal(ctx, err, "error listing tokens"), + }, nil + } + return &invitepb.ListInviteTokensResponse{ + Status: status.NewOK(ctx), + InviteTokens: tokens, + }, nil +} + func (s *service) ForwardInvite(ctx context.Context, req *invitepb.ForwardInviteRequest) (*invitepb.ForwardInviteResponse, error) { user := ctxpkg.ContextMustGetUser(ctx) diff --git a/internal/http/services/ocmd/protocols_test.go b/internal/http/services/ocmd/protocols_test.go index f6bbecb6cd..20c20f9a9a 100644 --- a/internal/http/services/ocmd/protocols_test.go +++ b/internal/http/services/ocmd/protocols_test.go @@ -96,13 +96,32 @@ func TestUnmarshalProtocol(t *testing.T) { } if tt.err == "" { - if !reflect.DeepEqual(got, tt.expected) { + if !protocolsEqual(got, tt.expected) { t.Fatalf("result does not match with expected. got=%+v expected=%+v", render.AsCode(got), render.AsCode(tt.expected)) } } } } +func protocolsToMap(p Protocols) map[string]Protocol { + m := make(map[string]Protocol) + for _, prot := range p { + switch prot.(type) { + case *WebDAV: + m["webdav"] = prot + case *Webapp: + m["webapp"] = prot + case *Datatx: + m["datatx"] = prot + } + } + return m +} + +func protocolsEqual(p1, p2 Protocols) bool { + return reflect.DeepEqual(protocolsToMap(p1), protocolsToMap(p2)) +} + func TestMarshalProtocol(t *testing.T) { tests := []struct { in Protocols diff --git a/internal/http/services/sciencemesh/sciencemesh.go b/internal/http/services/sciencemesh/sciencemesh.go index 261612b2f1..8dbd6ace1a 100644 --- a/internal/http/services/sciencemesh/sciencemesh.go +++ b/internal/http/services/sciencemesh/sciencemesh.go @@ -88,6 +88,7 @@ func (s *svc) routerInit() error { } s.router.Get("/generate-invite", tokenHandler.Generate) + s.router.Get("/list-invite", tokenHandler.ListInvite) s.router.Post("/accept-invite", tokenHandler.AcceptInvite) s.router.Get("/find-accepted-users", tokenHandler.FindAccepted) diff --git a/internal/http/services/sciencemesh/token.go b/internal/http/services/sciencemesh/token.go index 15fbc51fee..e0f61d295e 100644 --- a/internal/http/services/sciencemesh/token.go +++ b/internal/http/services/sciencemesh/token.go @@ -202,3 +202,26 @@ func (h *tokenHandler) FindAccepted(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } + +func (h *tokenHandler) ListInvite(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + res, err := h.gatewayClient.ListInviteTokens(ctx, &invitepb.ListInviteTokensRequest{}) + if err != nil { + reqres.WriteError(w, r, reqres.APIErrorServerError, "error listing tokens", err) + return + } + + if res.Status.Code != rpc.Code_CODE_OK { + reqres.WriteError(w, r, reqres.APIErrorServerError, res.Status.Message, errors.New(res.Status.Message)) + return + } + + if err := json.NewEncoder(w).Encode(res.InviteTokens); err != nil { + reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) +} diff --git a/pkg/ocm/invite/invite.go b/pkg/ocm/invite/invite.go index 7dfd9ac2f7..777f358920 100644 --- a/pkg/ocm/invite/invite.go +++ b/pkg/ocm/invite/invite.go @@ -34,6 +34,9 @@ type Repository interface { // GetToken gets the token from the repository. GetToken(ctx context.Context, token string) (*invitepb.InviteToken, error) + // ListTokens gets the valid tokens from the repository (i.e. not expired). + ListTokens(ctx context.Context, initiator *userpb.UserId) ([]*invitepb.InviteToken, error) + // AddRemoteUser stores the remote user. AddRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUser *userpb.User) error diff --git a/pkg/ocm/invite/repository/json/json.go b/pkg/ocm/invite/repository/json/json.go index 5b8ec6e37d..cb3ac11168 100644 --- a/pkg/ocm/invite/repository/json/json.go +++ b/pkg/ocm/invite/repository/json/json.go @@ -32,6 +32,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/ocm/invite" "github.com/cs3org/reva/pkg/ocm/invite/repository/registry" + "github.com/cs3org/reva/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -166,6 +167,19 @@ func (m *manager) GetToken(ctx context.Context, token string) (*invitepb.InviteT return nil, invite.ErrTokenNotFound } +func (m *manager) ListTokens(ctx context.Context, initiator *userpb.UserId) ([]*invitepb.InviteToken, error) { + m.RLock() + defer m.RUnlock() + + tokens := []*invitepb.InviteToken{} + for _, token := range m.model.Invites { + if utils.UserEqual(token.UserId, initiator) { + tokens = append(tokens, token) + } + } + return tokens, nil +} + func (m *manager) AddRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUser *userpb.User) error { m.Lock() defer m.Unlock() diff --git a/pkg/ocm/invite/repository/memory/memory.go b/pkg/ocm/invite/repository/memory/memory.go index c86da017b9..34bbf6a403 100644 --- a/pkg/ocm/invite/repository/memory/memory.go +++ b/pkg/ocm/invite/repository/memory/memory.go @@ -28,6 +28,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/ocm/invite" "github.com/cs3org/reva/pkg/ocm/invite/repository/registry" + "github.com/cs3org/reva/pkg/utils" ) func init() { @@ -59,6 +60,18 @@ func (m *manager) GetToken(ctx context.Context, token string) (*invitepb.InviteT return nil, invite.ErrTokenNotFound } +func (m *manager) ListTokens(ctx context.Context, initiator *userpb.UserId) ([]*invitepb.InviteToken, error) { + tokens := []*invitepb.InviteToken{} + m.Invites.Range(func(_, value any) bool { + token := value.(*invitepb.InviteToken) + if utils.UserEqual(token.UserId, initiator) { + tokens = append(tokens, token) + } + return true + }) + return tokens, nil +} + func (m *manager) AddRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUser *userpb.User) error { usersList, ok := m.AcceptedUsers.Load(initiator) acceptedUsers := usersList.([]*userpb.User) diff --git a/pkg/ocm/invite/repository/sql/sql.go b/pkg/ocm/invite/repository/sql/sql.go index 21c8c9bb7c..31181bba7d 100644 --- a/pkg/ocm/invite/repository/sql/sql.go +++ b/pkg/ocm/invite/repository/sql/sql.go @@ -124,6 +124,10 @@ func (m *mgr) GetToken(ctx context.Context, token string) (*invitepb.InviteToken } return nil, err } + return convertToInviteToken(tkn), nil +} + +func convertToInviteToken(tkn dbToken) *invitepb.InviteToken { return &invitepb.InviteToken{ Token: tkn.Token, UserId: conversions.ExtractUserID(tkn.Initiator), @@ -131,7 +135,27 @@ func (m *mgr) GetToken(ctx context.Context, token string) (*invitepb.InviteToken Seconds: uint64(tkn.Expiration.Unix()), }, Description: tkn.Description, - }, nil + } +} + +func (m *mgr) ListTokens(ctx context.Context, initiator *userpb.UserId) ([]*invitepb.InviteToken, error) { + query := "SELECT token, initiator, expiration, description FROM ocm_tokens WHERE initiator=?" + + tokens := []*invitepb.InviteToken{} + rows, err := m.db.QueryContext(ctx, query, conversions.FormatUserID(initiator)) + if err != nil { + return nil, err + } + + var tkn dbToken + for rows.Next() { + if err := rows.Scan(&tkn.Token, &tkn.Initiator, &tkn.Expiration, &tkn.Description); err != nil { + continue + } + tokens = append(tokens, convertToInviteToken(tkn)) + } + + return tokens, nil } // AddRemoteUser stores the remote user.