From 1793232430c2d2ffcce1df5347c7df7480fad6dd Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Wed, 26 May 2021 19:28:09 +0200 Subject: [PATCH 01/23] cli: token remove --- cmd/reva/app-tokens-remove.go | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 cmd/reva/app-tokens-remove.go diff --git a/cmd/reva/app-tokens-remove.go b/cmd/reva/app-tokens-remove.go new file mode 100644 index 0000000000..6fdf63fd2a --- /dev/null +++ b/cmd/reva/app-tokens-remove.go @@ -0,0 +1,63 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "io" + + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" +) + +func appTokensRemoveCommand() *command { + cmd := newCommand("token-remove") + cmd.Description = func() string { return "remove an application token" } + cmd.Usage = func() string { return "Usage: token-remove " } + + cmd.Action = func(w ...io.Writer) error { + if cmd.NArg() != 1 { + return errtypes.BadRequest("Invalid arguments: " + cmd.Usage()) + } + + token := cmd.Arg(0) + ctx := getAuthContext() + + client, err := getClient() + if err != nil { + return err + } + + response, err := client.InvalidateAppPassword(ctx, &applicationsv1beta1.InvalidateAppPasswordRequest{ + Password: token, + }) + + if err != nil { + return err + } + + if response.Status.Code != rpc.Code_CODE_OK { + return formatError(response.Status) + } + + return nil + } + + return cmd +} From 14f42ea320e52654622a62727f747b7ba3bb8649 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Wed, 26 May 2021 19:28:18 +0200 Subject: [PATCH 02/23] cli: tokens list --- cmd/reva/app-tokens-list.go | 161 ++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 cmd/reva/app-tokens-list.go diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go new file mode 100644 index 0000000000..b3ccd7abe4 --- /dev/null +++ b/cmd/reva/app-tokens-list.go @@ -0,0 +1,161 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "encoding/gob" + "io" + "os" + "time" + + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/jedib0t/go-pretty/table" +) + +type AppTokenListOpts struct { + Long bool + All bool + OnlyExpired bool + ApplicationFilter string +} + +var appTokenListOpts *AppTokenListOpts = &AppTokenListOpts{} + +func appTokensListCommand() *command { + cmd := newCommand("token-list") + cmd.Description = func() string { return "list all the application tokens" } + cmd.Usage = func() string { return "Usage: token-list [-flags]" } + + cmd.BoolVar(&appTokenListOpts.Long, "l", false, "long listing") + cmd.BoolVar(&appTokenListOpts.All, "a", false, "print all tokens, also the expired") + cmd.BoolVar(&appTokenListOpts.OnlyExpired, "e", false, "print only expired token") + cmd.StringVar(&appTokenListOpts.ApplicationFilter, "n", "", "filter by application name") + + shortHeader := table.Row{"Password", "Label", "Scope", "Expiration"} + longHeader := table.Row{"Password", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} + + cmd.ResetFlags = func() { + appTokenListOpts.All, appTokenListOpts.Long = false, false + } + + cmd.Action = func(w ...io.Writer) error { + + client, err := getClient() + if err != nil { + return err + } + + ctx := getAuthContext() + listResponse, err := client.ListAppPasswords(ctx, &applicationsv1beta1.ListAppPasswordsRequest{}) + + if err != nil { + return err + } + + if listResponse.Status.Code != rpc.Code_CODE_OK { + return formatError(listResponse.Status) + } + + listPw := filter(listResponse.AppPasswords, appTokenListOpts) + + if len(w) == 0 { + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + + if appTokenListOpts.Long { + t.AppendHeader(longHeader) + } else { + t.AppendHeader(shortHeader) + } + + for _, pw := range listPw { + if appTokenListOpts.Long { + t.AppendRow(table.Row{pw.Password, pw.TokenScope, pw.Label, pw.Expiration, pw.Ctime, pw.Utime}) + } else { + t.AppendRow(table.Row{pw.Password, pw.Label, pw.TokenScope, pw.Expiration}) + } + } + + t.Render() + + } else { + enc := gob.NewEncoder(w[0]) + if err := enc.Encode(listPw); err != nil { + return err + } + } + return nil + } + return cmd +} + +// Filter the list of app password, based on the option selected by the user +func filter(listPw []*applicationsv1beta1.AppPassword, opts *AppTokenListOpts) (filtered []*applicationsv1beta1.AppPassword) { + var f Filter + + f = &FilterByNone{} + if opts.OnlyExpired { + f = &FilterByExpired{other: &f} + } + if opts.ApplicationFilter != "" { + f = &FilterByApplicationName{other: &f} + } + if opts.All { + f = &FilterByNone{} + } + + for _, pw := range listPw { + if f.In(pw) { + filtered = append(filtered, pw) + } + } + return +} + +type Filter interface { + In(*applicationsv1beta1.AppPassword) bool +} + +type FilterByApplicationName struct { + name string + other *Filter +} +type FilterByNone struct{} +type FilterByExpired struct { + other *Filter +} + +func (f *FilterByApplicationName) In(pw *applicationsv1beta1.AppPassword) bool { + for app := range pw.TokenScope { + if app == f.name { + return (*f.other).In(pw) + } + } + return false +} + +func (f *FilterByNone) In(pw *applicationsv1beta1.AppPassword) bool { + return true +} + +func (f *FilterByExpired) In(pw *applicationsv1beta1.AppPassword) bool { + return (*f.other).In(pw) && pw.Expiration != nil && pw.Expiration.Seconds >= uint64(time.Now().Unix()) +} From 84e8753edc5e3674a98046c0b5faa7ad81e57514 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Wed, 26 May 2021 19:28:31 +0200 Subject: [PATCH 03/23] cli: token create --- cmd/reva/app-tokens-create.go | 93 +++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 cmd/reva/app-tokens-create.go diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go new file mode 100644 index 0000000000..ba1ec2bcfe --- /dev/null +++ b/cmd/reva/app-tokens-create.go @@ -0,0 +1,93 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "io" + "strings" + + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + v1beta11 "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" +) + +type AppTokenCreateOpts struct { + Expiration string + Label string + Path string + Share string + Unlimited bool +} + +var appTokensCreateOpts *AppTokenCreateOpts = &AppTokenCreateOpts{} + +const layoutTime = "2006-01-02T15:04" + +func appTokensCreateCommand() *command { + cmd := newCommand("token-create") + cmd.Description = func() string { return "create a new application tokens" } + cmd.Usage = func() string { return "Usage: token-create" } + + cmd.StringVar(&appTokensCreateOpts.Label, "label", "", "set a label") + cmd.StringVar(&appTokensCreateOpts.Expiration, "expiration", "", "set expiration time (format )") + cmd.StringVar(&appTokensCreateOpts.Path, "path", "", "TODO") + cmd.StringVar(&appTokensCreateOpts.Share, "share", "", "TODO") + cmd.BoolVar(&appTokensCreateOpts.Unlimited, "all", false, "TODO") + + cmd.ResetFlags = func() { + // TODO: reset flags + } + + cmd.Action = func(w ...io.Writer) error { + + client, err := getClient() + if err != nil { + return err + } + + ctx := getAuthContext() + + scope, err := getScope(appTokensCreateOpts) + if err != nil { + return err + } + + client.GenerateAppPassword(ctx, &applicationsv1beta1.GenerateAppPasswordRequest{ + Expiration: nil, // TODO: add expiration time + Label: appTokensCreateOpts.Label, + TokenScope: scope, + }) + + return nil + } + + return cmd +} + +func getScope(opts *AppTokenCreateOpts) (map[string]*v1beta11.Scope, error) { + switch { + case opts.Path != "": + // TODO: verify path format + // path = /path/a/b:rw + pathPerm := strings.Split(opts.Path, ":") + path, perm := pathPerm[0], pathPerm[1] + + } + + return nil, nil +} From 3e8eb017ab8cb8cc1f6d8a43c4eede18a1dd9009 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Wed, 26 May 2021 19:28:44 +0200 Subject: [PATCH 04/23] cli: created commands in main file --- cmd/reva/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/reva/main.go b/cmd/reva/main.go index ade6772e0c..5ffd2921af 100644 --- a/cmd/reva/main.go +++ b/cmd/reva/main.go @@ -84,6 +84,9 @@ var ( transferCreateCommand(), transferGetStatusCommand(), transferCancelCommand(), + appTokensListCommand(), + appTokensRemoveCommand(), + appTokensCreateCommand(), helpCommand(), } ) From 6628f6ca1769937967be5b70808488e0192a18ff Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Thu, 27 May 2021 17:23:55 +0200 Subject: [PATCH 05/23] cli: create token scope --- cmd/reva/app-tokens-create.go | 122 ++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 14 deletions(-) diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index ba1ec2bcfe..b7f3581f2d 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -19,11 +19,21 @@ package main import ( + "context" "io" + "reflect" "strings" - - applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" - v1beta11 "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + "time" + + authapp "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + share "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/auth/scope" + "github.com/cs3org/reva/pkg/errtypes" ) type AppTokenCreateOpts struct { @@ -45,12 +55,14 @@ func appTokensCreateCommand() *command { cmd.StringVar(&appTokensCreateOpts.Label, "label", "", "set a label") cmd.StringVar(&appTokensCreateOpts.Expiration, "expiration", "", "set expiration time (format )") - cmd.StringVar(&appTokensCreateOpts.Path, "path", "", "TODO") - cmd.StringVar(&appTokensCreateOpts.Share, "share", "", "TODO") - cmd.BoolVar(&appTokensCreateOpts.Unlimited, "all", false, "TODO") + // TODO(gmgigi96): add support for multiple paths and shares for the same token + cmd.StringVar(&appTokensCreateOpts.Path, "path", "", "create a token on a file (format path:[r|w])") + cmd.StringVar(&appTokensCreateOpts.Share, "share", "", "create a token for a share (format shareid:[r|w])") + cmd.BoolVar(&appTokensCreateOpts.Unlimited, "all", false, "create a token with an unlimited scope") cmd.ResetFlags = func() { - // TODO: reset flags + s := reflect.ValueOf(appTokensCreateOpts).Elem() + s.Set(reflect.Zero(s.Type())) } cmd.Action = func(w ...io.Writer) error { @@ -62,13 +74,25 @@ func appTokensCreateCommand() *command { ctx := getAuthContext() - scope, err := getScope(appTokensCreateOpts) + scope, err := getScope(ctx, client, appTokensCreateOpts) if err != nil { return err } - client.GenerateAppPassword(ctx, &applicationsv1beta1.GenerateAppPasswordRequest{ - Expiration: nil, // TODO: add expiration time + // parse eventually expiration time + var expiration *types.Timestamp + if appTokensCreateOpts.Expiration != "" { + exp, err := time.Parse(layoutTime, appTokensCreateOpts.Expiration) + if err != nil { + return err + } + expiration = &types.Timestamp{ + Seconds: uint64(exp.Unix()), + } + } + + client.GenerateAppPassword(ctx, &authapp.GenerateAppPasswordRequest{ + Expiration: expiration, Label: appTokensCreateOpts.Label, TokenScope: scope, }) @@ -79,15 +103,85 @@ func appTokensCreateCommand() *command { return cmd } -func getScope(opts *AppTokenCreateOpts) (map[string]*v1beta11.Scope, error) { +func getScope(ctx context.Context, client gateway.GatewayAPIClient, opts *AppTokenCreateOpts) (map[string]*authpb.Scope, error) { switch { + case opts.Share != "": + // TODO(gmgigi96): verify format + // share = xxxx:[r|w] + shareIDPerm := strings.Split(opts.Share, ":") + shareID, perm := shareIDPerm[0], shareIDPerm[1] + return getPublicShareScope(ctx, client, shareID, perm) case opts.Path != "": - // TODO: verify path format - // path = /path/a/b:rw + // TODO(gmgigi96): verify format + // path = /home/a/b:[r|w] pathPerm := strings.Split(opts.Path, ":") path, perm := pathPerm[0], pathPerm[1] - + return getPathScope(ctx, client, path, perm) + case opts.Unlimited: + return scope.GetOwnerScope() } return nil, nil } + +func getPublicShareScope(ctx context.Context, client gateway.GatewayAPIClient, shareID, perm string) (map[string]*authpb.Scope, error) { + role, err := parsePermission(perm) + if err != nil { + return nil, err + } + + publicShareResponse, err := client.GetPublicShare(ctx, &share.GetPublicShareRequest{ + Ref: &share.PublicShareReference{ + Spec: &share.PublicShareReference_Id{ + Id: &share.PublicShareId{ + OpaqueId: shareID, + }, + }, + }, + }) + + if err != nil { + return nil, err + } + if publicShareResponse.Status.Code != rpc.Code_CODE_OK { + return nil, formatError(publicShareResponse.Status) + } + + return scope.GetPublicShareScope(publicShareResponse.GetShare(), role) +} + +func getPathScope(ctx context.Context, client gateway.GatewayAPIClient, path, perm string) (map[string]*authpb.Scope, error) { + role, err := parsePermission(perm) + if err != nil { + return nil, err + } + + statResponse, err := client.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: path, + }, + }, + }) + + if err != nil { + return nil, err + } + if statResponse.Status.Code != rpc.Code_CODE_OK { + return nil, formatError(statResponse.Status) + } + + return scope.GetResourceInfoScope(statResponse.GetInfo(), role) +} + +// parse permission string in the form of "rw" to create a role +func parsePermission(perm string) (authpb.Role, error) { + switch perm { + case "r": + return authpb.Role_ROLE_VIEWER, nil + case "w": + return authpb.Role_ROLE_EDITOR, nil + default: + return authpb.Role_ROLE_INVALID, errtypes.BadRequest("not recognised permission") + } +} From f7c65a61692b35d46ecf5acc4ab2520b236f73a5 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Thu, 27 May 2021 18:55:12 +0200 Subject: [PATCH 06/23] cli: error check in token creation --- cmd/reva/app-tokens-create.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index b7f3581f2d..445538484a 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -20,6 +20,8 @@ package main import ( "context" + "encoding/gob" + "fmt" "io" "reflect" "strings" @@ -46,7 +48,7 @@ type AppTokenCreateOpts struct { var appTokensCreateOpts *AppTokenCreateOpts = &AppTokenCreateOpts{} -const layoutTime = "2006-01-02T15:04" +const layoutTime = "2006-01-02" func appTokensCreateCommand() *command { cmd := newCommand("token-create") @@ -54,7 +56,7 @@ func appTokensCreateCommand() *command { cmd.Usage = func() string { return "Usage: token-create" } cmd.StringVar(&appTokensCreateOpts.Label, "label", "", "set a label") - cmd.StringVar(&appTokensCreateOpts.Expiration, "expiration", "", "set expiration time (format )") + cmd.StringVar(&appTokensCreateOpts.Expiration, "expiration", "", "set expiration time (format )") // TODO(gmgigi96): add support for multiple paths and shares for the same token cmd.StringVar(&appTokensCreateOpts.Path, "path", "", "create a token on a file (format path:[r|w])") cmd.StringVar(&appTokensCreateOpts.Share, "share", "", "create a token for a share (format shareid:[r|w])") @@ -91,12 +93,28 @@ func appTokensCreateCommand() *command { } } - client.GenerateAppPassword(ctx, &authapp.GenerateAppPasswordRequest{ + generateAppPasswordResponse, err := client.GenerateAppPassword(ctx, &authapp.GenerateAppPasswordRequest{ Expiration: expiration, Label: appTokensCreateOpts.Label, TokenScope: scope, }) + if err != nil { + return err + } + if generateAppPasswordResponse.Status.Code != rpc.Code_CODE_OK { + return formatError(generateAppPasswordResponse.Status) + } + + if len(w) == 0 { + fmt.Println(generateAppPasswordResponse.String()) + } else { + enc := gob.NewEncoder(w[0]) + if err := enc.Encode(generateAppPasswordResponse.AppPassword); err != nil { + return err + } + } + return nil } From fa9c105fd38a79523fd4b9e63559339806f4ae79 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Thu, 27 May 2021 18:56:05 +0200 Subject: [PATCH 07/23] cli: reset flags and improved filters in tokens-list cmd --- cmd/reva/app-tokens-list.go | 45 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index b3ccd7abe4..9fce0f113f 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -22,6 +22,7 @@ import ( "encoding/gob" "io" "os" + "reflect" "time" applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" @@ -52,7 +53,8 @@ func appTokensListCommand() *command { longHeader := table.Row{"Password", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} cmd.ResetFlags = func() { - appTokenListOpts.All, appTokenListOpts.Long = false, false + s := reflect.ValueOf(appTokenListOpts).Elem() + s.Set(reflect.Zero(s.Type())) } cmd.Action = func(w ...io.Writer) error { @@ -109,21 +111,24 @@ func appTokensListCommand() *command { // Filter the list of app password, based on the option selected by the user func filter(listPw []*applicationsv1beta1.AppPassword, opts *AppTokenListOpts) (filtered []*applicationsv1beta1.AppPassword) { - var f Filter + var filters Filters - f = &FilterByNone{} + //TODO: add label filter if opts.OnlyExpired { - f = &FilterByExpired{other: &f} + filters = append(filters, &FilterByExpired{}) + } else { + filters = append(filters, &FilterByNotExpired{}) } if opts.ApplicationFilter != "" { - f = &FilterByApplicationName{other: &f} + filters = append(filters, &FilterByApplicationName{name: opts.ApplicationFilter}) } if opts.All { - f = &FilterByNone{} + // discard all the filters + filters = []Filter{&FilterByNone{}} } for _, pw := range listPw { - if f.In(pw) { + if filters.In(pw) { filtered = append(filtered, pw) } } @@ -135,18 +140,17 @@ type Filter interface { } type FilterByApplicationName struct { - name string - other *Filter + name string } type FilterByNone struct{} -type FilterByExpired struct { - other *Filter -} +type FilterByExpired struct{} +type FilterByNotExpired struct{} +type Filters []Filter func (f *FilterByApplicationName) In(pw *applicationsv1beta1.AppPassword) bool { for app := range pw.TokenScope { if app == f.name { - return (*f.other).In(pw) + return true } } return false @@ -157,5 +161,18 @@ func (f *FilterByNone) In(pw *applicationsv1beta1.AppPassword) bool { } func (f *FilterByExpired) In(pw *applicationsv1beta1.AppPassword) bool { - return (*f.other).In(pw) && pw.Expiration != nil && pw.Expiration.Seconds >= uint64(time.Now().Unix()) + return pw.Expiration != nil && pw.Expiration.Seconds <= uint64(time.Now().Unix()) +} + +func (f *FilterByNotExpired) In(pw *applicationsv1beta1.AppPassword) bool { + return !(&FilterByExpired{}).In(pw) +} + +func (f Filters) In(pw *applicationsv1beta1.AppPassword) bool { + for _, filter := range f { + if !filter.In(pw) { + return false + } + } + return true } From d17d0e3f223d611110f740b423bca845b11ee7f7 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 11:56:38 +0200 Subject: [PATCH 08/23] cli: better formatted values in token list --- cmd/reva/app-tokens-list.go | 102 +++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index 9fce0f113f..6a3757ea15 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -20,13 +20,21 @@ package main import ( "encoding/gob" + "fmt" "io" "os" "reflect" + "strings" "time" - applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + applications "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + authpv "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" "github.com/jedib0t/go-pretty/table" ) @@ -35,6 +43,7 @@ type AppTokenListOpts struct { All bool OnlyExpired bool ApplicationFilter string + Label string } var appTokenListOpts *AppTokenListOpts = &AppTokenListOpts{} @@ -44,13 +53,14 @@ func appTokensListCommand() *command { cmd.Description = func() string { return "list all the application tokens" } cmd.Usage = func() string { return "Usage: token-list [-flags]" } - cmd.BoolVar(&appTokenListOpts.Long, "l", false, "long listing") - cmd.BoolVar(&appTokenListOpts.All, "a", false, "print all tokens, also the expired") - cmd.BoolVar(&appTokenListOpts.OnlyExpired, "e", false, "print only expired token") - cmd.StringVar(&appTokenListOpts.ApplicationFilter, "n", "", "filter by application name") + cmd.BoolVar(&appTokenListOpts.Long, "long", false, "long listing") + cmd.BoolVar(&appTokenListOpts.All, "all", false, "print all tokens, also the expired") + cmd.BoolVar(&appTokenListOpts.OnlyExpired, "expired", false, "print only expired token") + cmd.StringVar(&appTokenListOpts.ApplicationFilter, "sope", "", "filter by scope") + cmd.StringVar(&appTokenListOpts.Label, "label", "", "filter by label name") - shortHeader := table.Row{"Password", "Label", "Scope", "Expiration"} - longHeader := table.Row{"Password", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} + shortHeader := table.Row{"Token", "Label", "Scope", "Expiration"} + longHeader := table.Row{"Token", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} cmd.ResetFlags = func() { s := reflect.ValueOf(appTokenListOpts).Elem() @@ -65,7 +75,7 @@ func appTokensListCommand() *command { } ctx := getAuthContext() - listResponse, err := client.ListAppPasswords(ctx, &applicationsv1beta1.ListAppPasswordsRequest{}) + listResponse, err := client.ListAppPasswords(ctx, &applications.ListAppPasswordsRequest{}) if err != nil { return err @@ -89,10 +99,14 @@ func appTokensListCommand() *command { } for _, pw := range listPw { + scopeFormatted, err := prettyFormatScope(pw.TokenScope) + if err != nil { + return err + } if appTokenListOpts.Long { - t.AppendRow(table.Row{pw.Password, pw.TokenScope, pw.Label, pw.Expiration, pw.Ctime, pw.Utime}) + t.AppendRow(table.Row{pw.Password, scopeFormatted, pw.Label, formatTime(pw.Expiration), formatTime(pw.Ctime), formatTime(pw.Utime)}) } else { - t.AppendRow(table.Row{pw.Password, pw.Label, pw.TokenScope, pw.Expiration}) + t.AppendRow(table.Row{pw.Password, pw.Label, scopeFormatted, formatTime(pw.Expiration)}) } } @@ -109,11 +123,53 @@ func appTokensListCommand() *command { return cmd } +func formatTime(t *types.Timestamp) string { + if t == nil { + return "" + } + return time.Unix(int64(t.Seconds), 0).String() +} + +func prettyFormatScope(scopeMap map[string]*authpv.Scope) (string, error) { + var scopeFormatted strings.Builder + for scopeType, scope := range scopeMap { + scopeStr, err := formatScope(scopeType, scope) + if err != nil { + return "", err + } + scopeFormatted.WriteString(scopeStr) + } + return scopeFormatted.String(), nil +} + +func formatScope(scopeType string, scope *authpv.Scope) (string, error) { + // TODO(gmgigi96): check decoder type + switch { + case strings.HasPrefix(scopeType, "user"): + // user scope + var ref provider.Reference + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &ref) + if err != nil { + return "", err + } + return fmt.Sprintf("%s %s", ref.String(), scope.Role.String()), nil + case strings.HasPrefix(scopeType, "publicshare"): + // public share + var pShare link.PublicShare + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &pShare) + if err != nil { + return "", err + } + return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil + default: + return "", errtypes.NotSupported("scope not yet supported") + } +} + // Filter the list of app password, based on the option selected by the user -func filter(listPw []*applicationsv1beta1.AppPassword, opts *AppTokenListOpts) (filtered []*applicationsv1beta1.AppPassword) { +func filter(listPw []*applications.AppPassword, opts *AppTokenListOpts) (filtered []*applications.AppPassword) { var filters Filters - //TODO: add label filter if opts.OnlyExpired { filters = append(filters, &FilterByExpired{}) } else { @@ -122,6 +178,9 @@ func filter(listPw []*applicationsv1beta1.AppPassword, opts *AppTokenListOpts) ( if opts.ApplicationFilter != "" { filters = append(filters, &FilterByApplicationName{name: opts.ApplicationFilter}) } + if opts.Label != "" { + filters = append(filters, &FilterByLabel{label: opts.Label}) + } if opts.All { // discard all the filters filters = []Filter{&FilterByNone{}} @@ -136,7 +195,7 @@ func filter(listPw []*applicationsv1beta1.AppPassword, opts *AppTokenListOpts) ( } type Filter interface { - In(*applicationsv1beta1.AppPassword) bool + In(*applications.AppPassword) bool } type FilterByApplicationName struct { @@ -145,9 +204,12 @@ type FilterByApplicationName struct { type FilterByNone struct{} type FilterByExpired struct{} type FilterByNotExpired struct{} +type FilterByLabel struct { + label string +} type Filters []Filter -func (f *FilterByApplicationName) In(pw *applicationsv1beta1.AppPassword) bool { +func (f *FilterByApplicationName) In(pw *applications.AppPassword) bool { for app := range pw.TokenScope { if app == f.name { return true @@ -156,19 +218,23 @@ func (f *FilterByApplicationName) In(pw *applicationsv1beta1.AppPassword) bool { return false } -func (f *FilterByNone) In(pw *applicationsv1beta1.AppPassword) bool { +func (f *FilterByNone) In(pw *applications.AppPassword) bool { return true } -func (f *FilterByExpired) In(pw *applicationsv1beta1.AppPassword) bool { +func (f *FilterByExpired) In(pw *applications.AppPassword) bool { return pw.Expiration != nil && pw.Expiration.Seconds <= uint64(time.Now().Unix()) } -func (f *FilterByNotExpired) In(pw *applicationsv1beta1.AppPassword) bool { +func (f *FilterByNotExpired) In(pw *applications.AppPassword) bool { return !(&FilterByExpired{}).In(pw) } -func (f Filters) In(pw *applicationsv1beta1.AppPassword) bool { +func (f *FilterByLabel) In(pw *applications.AppPassword) bool { + return f.label != "" && pw.Label == f.label +} + +func (f Filters) In(pw *applications.AppPassword) bool { for _, filter := range f { if !filter.In(pw) { return false From acf67889f4ac249b97b432b04b0773b2643941c8 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 12:31:08 +0200 Subject: [PATCH 09/23] refactoring: moved FormatScope in scope --- cmd/reva/app-tokens-list.go | 34 +++------------------------------- pkg/auth/scope/scope.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index 6a3757ea15..ddceb5dc7c 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -20,7 +20,6 @@ package main import ( "encoding/gob" - "fmt" "io" "os" "reflect" @@ -30,11 +29,8 @@ import ( applications "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" authpv "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/utils" + scope "github.com/cs3org/reva/pkg/auth/scope" "github.com/jedib0t/go-pretty/table" ) @@ -132,8 +128,8 @@ func formatTime(t *types.Timestamp) string { func prettyFormatScope(scopeMap map[string]*authpv.Scope) (string, error) { var scopeFormatted strings.Builder - for scopeType, scope := range scopeMap { - scopeStr, err := formatScope(scopeType, scope) + for scType, sc := range scopeMap { + scopeStr, err := scope.FormatScope(scType, sc) if err != nil { return "", err } @@ -142,30 +138,6 @@ func prettyFormatScope(scopeMap map[string]*authpv.Scope) (string, error) { return scopeFormatted.String(), nil } -func formatScope(scopeType string, scope *authpv.Scope) (string, error) { - // TODO(gmgigi96): check decoder type - switch { - case strings.HasPrefix(scopeType, "user"): - // user scope - var ref provider.Reference - err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &ref) - if err != nil { - return "", err - } - return fmt.Sprintf("%s %s", ref.String(), scope.Role.String()), nil - case strings.HasPrefix(scopeType, "publicshare"): - // public share - var pShare link.PublicShare - err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &pShare) - if err != nil { - return "", err - } - return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil - default: - return "", errtypes.NotSupported("scope not yet supported") - } -} - // Filter the list of app password, based on the option selected by the user func filter(listPw []*applications.AppPassword, opts *AppTokenListOpts) (filtered []*applications.AppPassword) { var filters Filters diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 35bdb29cfc..95163e36f2 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -19,9 +19,14 @@ package scope import ( + "fmt" "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" ) // Verifier is the function signature which every scope verifier should implement. @@ -51,3 +56,27 @@ func VerifyScope(scopeMap map[string]*authpb.Scope, resource interface{}) (bool, } return false, nil } + +func FormatScope(scopeType string, scope *authpb.Scope) (string, error) { + // TODO(gmgigi96): check decoder type + switch { + case strings.HasPrefix(scopeType, "user"): + // user scope + var ref provider.Reference + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &ref) + if err != nil { + return "", err + } + return fmt.Sprintf("%s %s", ref.String(), scope.Role.String()), nil + case strings.HasPrefix(scopeType, "publicshare"): + // public share + var pShare link.PublicShare + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &pShare) + if err != nil { + return "", err + } + return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil + default: + return "", errtypes.NotSupported("scope not yet supported") + } +} From 6e7ad73322b6961bb96547c295267f6936739a6b Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 14:06:16 +0200 Subject: [PATCH 10/23] refactoring: print table of app passwords in a separate function --- cmd/reva/app-tokens-list.go | 58 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index ddceb5dc7c..0a70ff8659 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -55,9 +55,6 @@ func appTokensListCommand() *command { cmd.StringVar(&appTokenListOpts.ApplicationFilter, "sope", "", "filter by scope") cmd.StringVar(&appTokenListOpts.Label, "label", "", "filter by label name") - shortHeader := table.Row{"Token", "Label", "Scope", "Expiration"} - longHeader := table.Row{"Token", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} - cmd.ResetFlags = func() { s := reflect.ValueOf(appTokenListOpts).Elem() s.Set(reflect.Zero(s.Type())) @@ -84,30 +81,10 @@ func appTokensListCommand() *command { listPw := filter(listResponse.AppPasswords, appTokenListOpts) if len(w) == 0 { - - t := table.NewWriter() - t.SetOutputMirror(os.Stdout) - - if appTokenListOpts.Long { - t.AppendHeader(longHeader) - } else { - t.AppendHeader(shortHeader) - } - - for _, pw := range listPw { - scopeFormatted, err := prettyFormatScope(pw.TokenScope) - if err != nil { - return err - } - if appTokenListOpts.Long { - t.AppendRow(table.Row{pw.Password, scopeFormatted, pw.Label, formatTime(pw.Expiration), formatTime(pw.Ctime), formatTime(pw.Utime)}) - } else { - t.AppendRow(table.Row{pw.Password, pw.Label, scopeFormatted, formatTime(pw.Expiration)}) - } + err = printTableAppPasswords(listPw, appTokenListOpts.Long) + if err != nil { + return err } - - t.Render() - } else { enc := gob.NewEncoder(w[0]) if err := enc.Encode(listPw); err != nil { @@ -119,6 +96,35 @@ func appTokensListCommand() *command { return cmd } +func printTableAppPasswords(listPw []*applications.AppPassword, long bool) error { + shortHeader := table.Row{"Token", "Label", "Scope", "Expiration"} + longHeader := table.Row{"Token", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + + if long { + t.AppendHeader(longHeader) + } else { + t.AppendHeader(shortHeader) + } + + for _, pw := range listPw { + scopeFormatted, err := prettyFormatScope(pw.TokenScope) + if err != nil { + return err + } + if long { + t.AppendRow(table.Row{pw.Password, scopeFormatted, pw.Label, formatTime(pw.Expiration), formatTime(pw.Ctime), formatTime(pw.Utime)}) + } else { + t.AppendRow(table.Row{pw.Password, pw.Label, scopeFormatted, formatTime(pw.Expiration)}) + } + } + + t.Render() + return nil +} + func formatTime(t *types.Timestamp) string { if t == nil { return "" From 1d4bfa2eeaeda66afddf75a2e4ae726cd874fb97 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 14:06:42 +0200 Subject: [PATCH 11/23] cli: better print of the new token --- cmd/reva/app-tokens-create.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index 445538484a..ed5c4fb626 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -21,7 +21,6 @@ package main import ( "context" "encoding/gob" - "fmt" "io" "reflect" "strings" @@ -69,6 +68,11 @@ func appTokensCreateCommand() *command { cmd.Action = func(w ...io.Writer) error { + err := checkOpts(appTokensCreateOpts) + if err != nil { + return err + } + client, err := getClient() if err != nil { return err @@ -106,11 +110,16 @@ func appTokensCreateCommand() *command { return formatError(generateAppPasswordResponse.Status) } + pw := generateAppPasswordResponse.AppPassword + if len(w) == 0 { - fmt.Println(generateAppPasswordResponse.String()) + err = printTableAppPasswords([]*authapp.AppPassword{pw}, true) + if err != nil { + return err + } } else { enc := gob.NewEncoder(w[0]) - if err := enc.Encode(generateAppPasswordResponse.AppPassword); err != nil { + if err := enc.Encode(pw); err != nil { return err } } @@ -203,3 +212,10 @@ func parsePermission(perm string) (authpb.Role, error) { return authpb.Role_ROLE_INVALID, errtypes.BadRequest("not recognised permission") } } + +func checkOpts(opts *AppTokenCreateOpts) error { + if opts.Share == "" && opts.Path == "" && !opts.Unlimited { + return errtypes.BadRequest("specify a token scope") + } + return nil +} From 5b651c91b0f02da95163912575486446074a5cf2 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 14:19:33 +0200 Subject: [PATCH 12/23] appauth cli: add changelog --- changelog/unreleased/app-passwords-cli.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog/unreleased/app-passwords-cli.md diff --git a/changelog/unreleased/app-passwords-cli.md b/changelog/unreleased/app-passwords-cli.md new file mode 100644 index 0000000000..6211cc0d80 --- /dev/null +++ b/changelog/unreleased/app-passwords-cli.md @@ -0,0 +1,6 @@ +Enhancement: Application passwords CLI + +This PR adds the CLI commands `token-list`, `token-create` and `token-remove` +to manage tokens with limited scope on behalf of registered users. + +https://github.com/cs3org/reva/pull/1719 From b5d3253573b6442e262d7f8dc9def2c27e40632d Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 14:21:11 +0200 Subject: [PATCH 13/23] go mod download --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 73363d27b2..a1625dd412 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,6 @@ github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20210527092012-82617367e09d h1:mQiARNvWPIPdqal7gWWTIVpC6C10aC9/2fVjCK9AIg4= -github.com/cs3org/go-cs3apis v0.0.0-20210527092012-82617367e09d/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cs3org/go-cs3apis v0.0.0-20210527092509-2b828e94ed4c h1:EaKDtDswzfWUr70xoN63sPLZyvdinkmXrjqc5AFhVZE= github.com/cs3org/go-cs3apis v0.0.0-20210527092509-2b828e94ed4c/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8= From 3d5fe9066e847231e546057b3e72e656d0fffea2 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 14:37:08 +0200 Subject: [PATCH 14/23] appauth: added default value for token strength and fix bug in token generation for small strength --- pkg/appauth/manager/json/json.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/appauth/manager/json/json.go b/pkg/appauth/manager/json/json.go index 1449adcac9..690e37e640 100644 --- a/pkg/appauth/manager/json/json.go +++ b/pkg/appauth/manager/json/json.go @@ -79,6 +79,9 @@ func (c *config) init() { if c.File == "" { c.File = "/var/tmp/reva/appauth.json" } + if c.TokenStrength == 0 { + c.TokenStrength = 16 + } } func parseConfig(m map[string]interface{}) (*config, error) { @@ -121,7 +124,7 @@ func loadOrCreate(file string) (*jsonManager, error) { } func (mgr *jsonManager) GenerateAppPassword(ctx context.Context, scope map[string]*authpb.Scope, label string, expiration *typespb.Timestamp) (*apppb.AppPassword, error) { - token, err := password.Generate(mgr.config.TokenStrength, 10, 10, false, false) + token, err := password.Generate(mgr.config.TokenStrength, mgr.config.TokenStrength/2, 0, false, false) if err != nil { return nil, errors.Wrap(err, "error creating new token") } From 5de0b174362ac4e146a680382b84cf299603a447 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 14:47:12 +0200 Subject: [PATCH 15/23] add comments and remove unnecessary exports --- cmd/reva/app-tokens-create.go | 30 ++++++++-------- cmd/reva/app-tokens-list.go | 68 +++++++++++++++++------------------ pkg/auth/scope/scope.go | 1 + 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index ed5c4fb626..56de9d0d4a 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -37,7 +37,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" ) -type AppTokenCreateOpts struct { +type appTokenCreateOpts struct { Expiration string Label string Path string @@ -45,7 +45,7 @@ type AppTokenCreateOpts struct { Unlimited bool } -var appTokensCreateOpts *AppTokenCreateOpts = &AppTokenCreateOpts{} +var createOpts *appTokenCreateOpts = &appTokenCreateOpts{} const layoutTime = "2006-01-02" @@ -54,21 +54,21 @@ func appTokensCreateCommand() *command { cmd.Description = func() string { return "create a new application tokens" } cmd.Usage = func() string { return "Usage: token-create" } - cmd.StringVar(&appTokensCreateOpts.Label, "label", "", "set a label") - cmd.StringVar(&appTokensCreateOpts.Expiration, "expiration", "", "set expiration time (format )") + cmd.StringVar(&createOpts.Label, "label", "", "set a label") + cmd.StringVar(&createOpts.Expiration, "expiration", "", "set expiration time (format )") // TODO(gmgigi96): add support for multiple paths and shares for the same token - cmd.StringVar(&appTokensCreateOpts.Path, "path", "", "create a token on a file (format path:[r|w])") - cmd.StringVar(&appTokensCreateOpts.Share, "share", "", "create a token for a share (format shareid:[r|w])") - cmd.BoolVar(&appTokensCreateOpts.Unlimited, "all", false, "create a token with an unlimited scope") + cmd.StringVar(&createOpts.Path, "path", "", "create a token on a file (format path:[r|w])") + cmd.StringVar(&createOpts.Share, "share", "", "create a token for a share (format shareid:[r|w])") + cmd.BoolVar(&createOpts.Unlimited, "all", false, "create a token with an unlimited scope") cmd.ResetFlags = func() { - s := reflect.ValueOf(appTokensCreateOpts).Elem() + s := reflect.ValueOf(createOpts).Elem() s.Set(reflect.Zero(s.Type())) } cmd.Action = func(w ...io.Writer) error { - err := checkOpts(appTokensCreateOpts) + err := checkOpts(createOpts) if err != nil { return err } @@ -80,15 +80,15 @@ func appTokensCreateCommand() *command { ctx := getAuthContext() - scope, err := getScope(ctx, client, appTokensCreateOpts) + scope, err := getScope(ctx, client, createOpts) if err != nil { return err } // parse eventually expiration time var expiration *types.Timestamp - if appTokensCreateOpts.Expiration != "" { - exp, err := time.Parse(layoutTime, appTokensCreateOpts.Expiration) + if createOpts.Expiration != "" { + exp, err := time.Parse(layoutTime, createOpts.Expiration) if err != nil { return err } @@ -99,7 +99,7 @@ func appTokensCreateCommand() *command { generateAppPasswordResponse, err := client.GenerateAppPassword(ctx, &authapp.GenerateAppPasswordRequest{ Expiration: expiration, - Label: appTokensCreateOpts.Label, + Label: createOpts.Label, TokenScope: scope, }) @@ -130,7 +130,7 @@ func appTokensCreateCommand() *command { return cmd } -func getScope(ctx context.Context, client gateway.GatewayAPIClient, opts *AppTokenCreateOpts) (map[string]*authpb.Scope, error) { +func getScope(ctx context.Context, client gateway.GatewayAPIClient, opts *appTokenCreateOpts) (map[string]*authpb.Scope, error) { switch { case opts.Share != "": // TODO(gmgigi96): verify format @@ -213,7 +213,7 @@ func parsePermission(perm string) (authpb.Role, error) { } } -func checkOpts(opts *AppTokenCreateOpts) error { +func checkOpts(opts *appTokenCreateOpts) error { if opts.Share == "" && opts.Path == "" && !opts.Unlimited { return errtypes.BadRequest("specify a token scope") } diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index 0a70ff8659..76eafd8881 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -34,7 +34,7 @@ import ( "github.com/jedib0t/go-pretty/table" ) -type AppTokenListOpts struct { +type appTokenListOpts struct { Long bool All bool OnlyExpired bool @@ -42,21 +42,21 @@ type AppTokenListOpts struct { Label string } -var appTokenListOpts *AppTokenListOpts = &AppTokenListOpts{} +var listOpts *appTokenListOpts = &appTokenListOpts{} func appTokensListCommand() *command { cmd := newCommand("token-list") cmd.Description = func() string { return "list all the application tokens" } cmd.Usage = func() string { return "Usage: token-list [-flags]" } - cmd.BoolVar(&appTokenListOpts.Long, "long", false, "long listing") - cmd.BoolVar(&appTokenListOpts.All, "all", false, "print all tokens, also the expired") - cmd.BoolVar(&appTokenListOpts.OnlyExpired, "expired", false, "print only expired token") - cmd.StringVar(&appTokenListOpts.ApplicationFilter, "sope", "", "filter by scope") - cmd.StringVar(&appTokenListOpts.Label, "label", "", "filter by label name") + cmd.BoolVar(&listOpts.Long, "long", false, "long listing") + cmd.BoolVar(&listOpts.All, "all", false, "print all tokens, also the expired") + cmd.BoolVar(&listOpts.OnlyExpired, "expired", false, "print only expired token") + cmd.StringVar(&listOpts.ApplicationFilter, "sope", "", "filter by scope") + cmd.StringVar(&listOpts.Label, "label", "", "filter by label name") cmd.ResetFlags = func() { - s := reflect.ValueOf(appTokenListOpts).Elem() + s := reflect.ValueOf(listOpts).Elem() s.Set(reflect.Zero(s.Type())) } @@ -78,10 +78,10 @@ func appTokensListCommand() *command { return formatError(listResponse.Status) } - listPw := filter(listResponse.AppPasswords, appTokenListOpts) + listPw := filterAppPasswords(listResponse.AppPasswords, listOpts) if len(w) == 0 { - err = printTableAppPasswords(listPw, appTokenListOpts.Long) + err = printTableAppPasswords(listPw, listOpts.Long) if err != nil { return err } @@ -145,49 +145,49 @@ func prettyFormatScope(scopeMap map[string]*authpv.Scope) (string, error) { } // Filter the list of app password, based on the option selected by the user -func filter(listPw []*applications.AppPassword, opts *AppTokenListOpts) (filtered []*applications.AppPassword) { - var filters Filters +func filterAppPasswords(listPw []*applications.AppPassword, opts *appTokenListOpts) (filtered []*applications.AppPassword) { + var filters filters if opts.OnlyExpired { - filters = append(filters, &FilterByExpired{}) + filters = append(filters, &filterByExpired{}) } else { - filters = append(filters, &FilterByNotExpired{}) + filters = append(filters, &filterByNotExpired{}) } if opts.ApplicationFilter != "" { - filters = append(filters, &FilterByApplicationName{name: opts.ApplicationFilter}) + filters = append(filters, &filterByApplicationName{name: opts.ApplicationFilter}) } if opts.Label != "" { - filters = append(filters, &FilterByLabel{label: opts.Label}) + filters = append(filters, &filterByLabel{label: opts.Label}) } if opts.All { // discard all the filters - filters = []Filter{&FilterByNone{}} + filters = []filter{&filterByNone{}} } for _, pw := range listPw { - if filters.In(pw) { + if filters.in(pw) { filtered = append(filtered, pw) } } return } -type Filter interface { - In(*applications.AppPassword) bool +type filter interface { + in(*applications.AppPassword) bool } -type FilterByApplicationName struct { +type filterByApplicationName struct { name string } -type FilterByNone struct{} -type FilterByExpired struct{} -type FilterByNotExpired struct{} -type FilterByLabel struct { +type filterByNone struct{} +type filterByExpired struct{} +type filterByNotExpired struct{} +type filterByLabel struct { label string } -type Filters []Filter +type filters []filter -func (f *FilterByApplicationName) In(pw *applications.AppPassword) bool { +func (f *filterByApplicationName) in(pw *applications.AppPassword) bool { for app := range pw.TokenScope { if app == f.name { return true @@ -196,25 +196,25 @@ func (f *FilterByApplicationName) In(pw *applications.AppPassword) bool { return false } -func (f *FilterByNone) In(pw *applications.AppPassword) bool { +func (f *filterByNone) in(pw *applications.AppPassword) bool { return true } -func (f *FilterByExpired) In(pw *applications.AppPassword) bool { +func (f *filterByExpired) in(pw *applications.AppPassword) bool { return pw.Expiration != nil && pw.Expiration.Seconds <= uint64(time.Now().Unix()) } -func (f *FilterByNotExpired) In(pw *applications.AppPassword) bool { - return !(&FilterByExpired{}).In(pw) +func (f *filterByNotExpired) in(pw *applications.AppPassword) bool { + return !(&filterByExpired{}).in(pw) } -func (f *FilterByLabel) In(pw *applications.AppPassword) bool { +func (f *filterByLabel) in(pw *applications.AppPassword) bool { return f.label != "" && pw.Label == f.label } -func (f Filters) In(pw *applications.AppPassword) bool { +func (f filters) in(pw *applications.AppPassword) bool { for _, filter := range f { - if !filter.In(pw) { + if !filter.in(pw) { return false } } diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index 95163e36f2..a3a5649143 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -57,6 +57,7 @@ func VerifyScope(scopeMap map[string]*authpb.Scope, resource interface{}) (bool, return false, nil } +// FormatScope create a pretty print of the scope func FormatScope(scopeType string, scope *authpb.Scope) (string, error) { // TODO(gmgigi96): check decoder type switch { From f5b42b82818a67aa6aee61e84ecd274b9bf4918c Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 17:54:07 +0200 Subject: [PATCH 16/23] appauth: save hashed pw instead of plain text pw --- pkg/appauth/manager/json/json.go | 52 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/pkg/appauth/manager/json/json.go b/pkg/appauth/manager/json/json.go index 690e37e640..8018fb640a 100644 --- a/pkg/appauth/manager/json/json.go +++ b/pkg/appauth/manager/json/json.go @@ -37,6 +37,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/sethvargo/go-password/password" + "golang.org/x/crypto/bcrypt" ) func init() { @@ -44,8 +45,9 @@ func init() { } type config struct { - File string `mapstructure:"file"` - TokenStrength int `mapstructure:"token_strength"` + File string `mapstructure:"file"` + TokenStrength int `mapstructure:"token_strength"` + PasswordHashCost int `mapstructure:"password_hash_cost"` } type jsonManager struct { @@ -82,6 +84,9 @@ func (c *config) init() { if c.TokenStrength == 0 { c.TokenStrength = 16 } + if c.PasswordHashCost == 0 { + c.PasswordHashCost = 11 + } } func parseConfig(m map[string]interface{}) (*config, error) { @@ -128,11 +133,16 @@ func (mgr *jsonManager) GenerateAppPassword(ctx context.Context, scope map[strin if err != nil { return nil, errors.Wrap(err, "error creating new token") } + tokenHashed, err := bcrypt.GenerateFromPassword([]byte(token), mgr.config.PasswordHashCost) + if err != nil { + return nil, errors.Wrap(err, "error creating new token") + } userID := user.ContextMustGetUser(ctx).GetId() ctime := now() + password := string(tokenHashed) appPass := &apppb.AppPassword{ - Password: token, + Password: password, TokenScope: scope, Label: label, Expiration: expiration, @@ -148,14 +158,16 @@ func (mgr *jsonManager) GenerateAppPassword(ctx context.Context, scope map[strin mgr.passwords[userID.String()] = make(map[string]*apppb.AppPassword) } - mgr.passwords[userID.String()][token] = appPass + mgr.passwords[userID.String()][password] = appPass err = mgr.save() if err != nil { return nil, errors.Wrap(err, "error saving new token") } - return appPass, nil + clonedAppPass := *appPass + clonedAppPass.Password = token + return &clonedAppPass, nil } func (mgr *jsonManager) ListAppPasswords(ctx context.Context) ([]*apppb.AppPassword, error) { @@ -202,20 +214,26 @@ func (mgr *jsonManager) GetAppPassword(ctx context.Context, userID *userpb.UserI return nil, errtypes.NotFound("password not found") } - pw, ok := appPassword[password] - if !ok { - return nil, errtypes.NotFound("password not found") - } - - if pw.Expiration != nil && pw.Expiration.Seconds != 0 && uint64(time.Now().Unix()) > pw.Expiration.Seconds { - return nil, errtypes.NotFound("password not found") + for hash, pw := range appPassword { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + if err == nil { + // password found + if pw.Expiration != nil && pw.Expiration.Seconds != 0 && uint64(time.Now().Unix()) > pw.Expiration.Seconds { + // password expired + return nil, errtypes.NotFound("password not found") + } + // password not expired + // update last used time + pw.Utime = now() + if err := mgr.save(); err != nil { + return nil, errors.Wrap(err, "error saving file") + } + + return pw, nil + } } - pw.Utime = now() - if err := mgr.save(); err != nil { - return nil, errors.Wrap(err, "error saving file") - } - return pw, nil + return nil, errtypes.NotFound("password not found") } func now() *typespb.Timestamp { From f0f26d4f7dd927f80f01b9f458b359ed77e7c795 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 17:54:24 +0200 Subject: [PATCH 17/23] appauth: change test with hashed pw saved in json --- pkg/appauth/manager/json/json_test.go | 123 ++++++++++++++++++-------- 1 file changed, 86 insertions(+), 37 deletions(-) diff --git a/pkg/appauth/manager/json/json_test.go b/pkg/appauth/manager/json/json_test.go index e42d73f5b5..2c51251cde 100644 --- a/pkg/appauth/manager/json/json_test.go +++ b/pkg/appauth/manager/json/json_test.go @@ -19,6 +19,7 @@ package json import ( + "bytes" "context" "encoding/json" "io/ioutil" @@ -34,6 +35,7 @@ import ( "github.com/cs3org/reva/pkg/user" "github.com/gdexlab/go-render/render" "github.com/sethvargo/go-password/password" + "golang.org/x/crypto/bcrypt" ) func TestNewManager(t *testing.T) { @@ -50,10 +52,12 @@ func TestNewManager(t *testing.T) { jsonOkFile := createTempFile(t, tempDir, "ok.json") defer jsonOkFile.Close() + hashToken, _ := bcrypt.GenerateFromPassword([]byte("1234"), 10) + dummyData := map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - "1234": { - Password: "1234", + string(hashToken): { + Password: string(hashToken), TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -86,13 +90,15 @@ func TestNewManager(t *testing.T) { { description: "New appauth manager from empty state file", configMap: map[string]interface{}{ - "file": jsonEmptyFile.Name(), - "token_strength": 10, + "file": jsonEmptyFile.Name(), + "token_strength": 10, + "password_hash_cost": 12, }, expected: &jsonManager{ config: &config{ - File: jsonEmptyFile.Name(), - TokenStrength: 10, + File: jsonEmptyFile.Name(), + TokenStrength: 10, + PasswordHashCost: 12, }, passwords: map[string]map[string]*apppb.AppPassword{}, }, @@ -100,13 +106,15 @@ func TestNewManager(t *testing.T) { { description: "New appauth manager from state file", configMap: map[string]interface{}{ - "file": jsonOkFile.Name(), - "token_strength": 10, + "file": jsonOkFile.Name(), + "token_strength": 10, + "password_hash_cost": 10, }, expected: &jsonManager{ config: &config{ - File: jsonOkFile.Name(), - TokenStrength: 10, + File: jsonOkFile.Name(), + TokenStrength: 10, + PasswordHashCost: 10, }, passwords: dummyData, }, @@ -145,10 +153,17 @@ func TestGenerateAppPassword(t *testing.T) { defer patchNow.Unpatch() defer patchPasswordGenerate.Unpatch() + generateFromPassword := monkey.Patch(bcrypt.GenerateFromPassword, func(pw []byte, n int) ([]byte, error) { + return append([]byte("hash:"), pw...), nil + }) + defer generateFromPassword.Restore() + hashTokenXXXX, _ := bcrypt.GenerateFromPassword([]byte("XXXX"), 11) + hashToken1234, _ := bcrypt.GenerateFromPassword([]byte(token), 11) + dummyData := map[string]map[string]*apppb.AppPassword{ userpb.User{Id: &userpb.UserId{Idp: "1"}, Username: "Test User1"}.Id.String(): { - "XXXX": { - Password: "XXXX", + string(hashTokenXXXX): { + Password: string(hashTokenXXXX), Label: "", User: &userpb.UserId{Idp: "1"}, Ctime: now, @@ -162,15 +177,25 @@ func TestGenerateAppPassword(t *testing.T) { testCases := []struct { description string prevStateJSON string + expected *apppb.AppPassword expectedState map[string]map[string]*apppb.AppPassword }{ { description: "GenerateAppPassword with empty state", prevStateJSON: `{}`, + expected: &apppb.AppPassword{ + Password: token, + TokenScope: nil, + Label: "label", + User: userTest.GetId(), + Expiration: nil, + Ctime: now, + Utime: now, + }, expectedState: map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - token: { - Password: token, + string(hashToken1234): { + Password: string(hashToken1234), TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -184,10 +209,19 @@ func TestGenerateAppPassword(t *testing.T) { { description: "GenerateAppPassword with not empty state", prevStateJSON: string(dummyDataJSON), + expected: &apppb.AppPassword{ + Password: token, + TokenScope: nil, + Label: "label", + User: userTest.GetId(), + Expiration: nil, + Ctime: now, + Utime: now, + }, expectedState: concatMaps(map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - token: { - Password: token, + string(hashToken1234): { + Password: string(hashToken1234), TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -207,8 +241,9 @@ func TestGenerateAppPassword(t *testing.T) { defer tmpFile.Close() fill(t, tmpFile, test.prevStateJSON) manager, err := New(map[string]interface{}{ - "file": tmpFile.Name(), - "token_strength": len(token), + "file": tmpFile.Name(), + "token_strength": len(token), + "password_hash_cost": 11, }) if err != nil { t.Fatal("error creating manager:", err) @@ -221,8 +256,8 @@ func TestGenerateAppPassword(t *testing.T) { // test state in memory - if !reflect.DeepEqual(pw, test.expectedState[userTest.GetId().String()][token]) { - t.Fatalf("apppassword differ: expected=%v got=%v", render.AsCode(test.expectedState[userTest.GetId().String()][token]), render.AsCode(pw)) + if !reflect.DeepEqual(pw, test.expected) { + t.Fatalf("apppassword differ: expected=%v got=%v", render.AsCode(test.expected), render.AsCode(pw)) } if !reflect.DeepEqual(manager.(*jsonManager).passwords, test.expectedState) { @@ -267,7 +302,7 @@ func TestListAppPasswords(t *testing.T) { defer patchNow.Unpatch() now := now() - token := "1234" + token := "hash:1234" dummyDataUser0 := map[string]map[string]*apppb.AppPassword{ user0Test.GetId().String(): { @@ -394,7 +429,7 @@ func TestInvalidateAppPassword(t *testing.T) { now := now() defer patchNow.Unpatch() - token := "1234" + token := "hash:1234" dummyDataUser1Token := map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { @@ -422,8 +457,8 @@ func TestInvalidateAppPassword(t *testing.T) { Ctime: now, Utime: now, }, - "XXXX": { - Password: "XXXX", + "hash:XXXX": { + Password: "hash:XXXX", TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -465,8 +500,8 @@ func TestInvalidateAppPassword(t *testing.T) { password: token, expectedState: map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - "XXXX": { - Password: "XXXX", + "hash:XXXX": { + Password: "hash:XXXX", TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -522,10 +557,24 @@ func TestGetAppPassword(t *testing.T) { now := now() token := "1234" + generateFromPassword := monkey.Patch(bcrypt.GenerateFromPassword, func(pw []byte, n int) ([]byte, error) { + return append([]byte("hash:"), pw...), nil + }) + compareHashAndPassword := monkey.Patch(bcrypt.CompareHashAndPassword, func(hash, pw []byte) error { + hashPw, _ := bcrypt.GenerateFromPassword(pw, 0) + if bytes.Equal(hashPw, hash) { + return nil + } + return bcrypt.ErrMismatchedHashAndPassword + }) + defer generateFromPassword.Restore() + defer compareHashAndPassword.Restore() + hashToken1234, _ := bcrypt.GenerateFromPassword([]byte(token), 11) + dummyDataUser1Token := map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - token: { - Password: token, + string(hashToken1234): { + Password: string(hashToken1234), TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -537,8 +586,8 @@ func TestGetAppPassword(t *testing.T) { dummyDataUserExpired := map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - token: { - Password: token, + string(hashToken1234): { + Password: string(hashToken1234), TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -552,8 +601,8 @@ func TestGetAppPassword(t *testing.T) { dummyDataUserFutureExpiration := map[string]map[string]*apppb.AppPassword{ userTest.GetId().String(): { - token: { - Password: token, + string(hashToken1234): { + Password: string(hashToken1234), TokenScope: nil, Label: "label", User: userTest.GetId(), @@ -571,8 +620,8 @@ func TestGetAppPassword(t *testing.T) { dummyDataDifferentUserToken := map[string]map[string]*apppb.AppPassword{ "OTHER_USER_ID": { - token: { - Password: token, + string(hashToken1234): { + Password: string(hashToken1234), TokenScope: nil, Label: "label", User: &userpb.UserId{Idp: "OTHER_USER_ID"}, @@ -599,14 +648,14 @@ func TestGetAppPassword(t *testing.T) { { description: "GetAppPassword with expired token", stateJSON: string(dummyDataUserExpiredJSON), - password: "TOKEN_NOT_EXISTS", + password: "1234", expectedState: nil, }, { description: "GetAppPassword with token with expiration set in the future", stateJSON: string(dummyDataUserFutureExpirationJSON), password: "1234", - expectedState: dummyDataUserFutureExpiration[userTest.GetId().String()][token], + expectedState: dummyDataUserFutureExpiration[userTest.GetId().String()][string(hashToken1234)], }, { description: "GetAppPassword with token that exists but different user", @@ -618,7 +667,7 @@ func TestGetAppPassword(t *testing.T) { description: "GetAppPassword with token that exists owned by user", stateJSON: string(dummyDataUser1TokenJSON), password: "1234", - expectedState: dummyDataUser1Token[userTest.GetId().String()][token], + expectedState: dummyDataUser1Token[userTest.GetId().String()][string(hashToken1234)], }, } From 8651667796a6cec5ff5f6923bf087f030e521ea8 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 18:40:46 +0200 Subject: [PATCH 18/23] scope: add pretty print to path scope --- pkg/auth/scope/scope.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index a3a5649143..d1943f9053 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -77,6 +77,13 @@ func FormatScope(scopeType string, scope *authpb.Scope) (string, error) { return "", err } return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil + case strings.HasPrefix(scopeType, "resourceinfo"): + var resInfo provider.ResourceInfo + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &resInfo) + if err != nil { + return "", err + } + return fmt.Sprintf("path:\"%s\" %s", resInfo.Path, scope.Role.String()), nil default: return "", errtypes.NotSupported("scope not yet supported") } From 7390f5ea668e97ed3eca844f1d528309cd3c7813 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Fri, 28 May 2021 19:23:46 +0200 Subject: [PATCH 19/23] cli: add support for multiple path and share in the same scope --- cmd/reva/app-tokens-create.go | 72 ++++++++++++++++++++++++++--------- cmd/reva/app-tokens-list.go | 3 +- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index 56de9d0d4a..1e2d4fb31e 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -40,11 +40,22 @@ import ( type appTokenCreateOpts struct { Expiration string Label string - Path string - Share string + Path stringSlice + Share stringSlice Unlimited bool } +type stringSlice []string + +func (ss *stringSlice) Set(value string) error { + *ss = append(*ss, value) + return nil +} + +func (ss *stringSlice) String() string { + return strings.Join([]string(*ss), ",") +} + var createOpts *appTokenCreateOpts = &appTokenCreateOpts{} const layoutTime = "2006-01-02" @@ -57,8 +68,8 @@ func appTokensCreateCommand() *command { cmd.StringVar(&createOpts.Label, "label", "", "set a label") cmd.StringVar(&createOpts.Expiration, "expiration", "", "set expiration time (format )") // TODO(gmgigi96): add support for multiple paths and shares for the same token - cmd.StringVar(&createOpts.Path, "path", "", "create a token on a file (format path:[r|w])") - cmd.StringVar(&createOpts.Share, "share", "", "create a token for a share (format shareid:[r|w])") + cmd.Var(&createOpts.Path, "path", "create a token for a file (format path:[r|w]). It is possible specify multiple times this flag") + cmd.Var(&createOpts.Share, "share", "create a token for a share (format shareid:[r|w]). It is possible specify multiple times this flags") cmd.BoolVar(&createOpts.Unlimited, "all", false, "create a token with an unlimited scope") cmd.ResetFlags = func() { @@ -131,24 +142,49 @@ func appTokensCreateCommand() *command { } func getScope(ctx context.Context, client gateway.GatewayAPIClient, opts *appTokenCreateOpts) (map[string]*authpb.Scope, error) { + var scopeList []map[string]*authpb.Scope switch { - case opts.Share != "": - // TODO(gmgigi96): verify format - // share = xxxx:[r|w] - shareIDPerm := strings.Split(opts.Share, ":") - shareID, perm := shareIDPerm[0], shareIDPerm[1] - return getPublicShareScope(ctx, client, shareID, perm) - case opts.Path != "": - // TODO(gmgigi96): verify format - // path = /home/a/b:[r|w] - pathPerm := strings.Split(opts.Path, ":") - path, perm := pathPerm[0], pathPerm[1] - return getPathScope(ctx, client, path, perm) case opts.Unlimited: return scope.GetOwnerScope() + case len(opts.Share) != 0: + // TODO(gmgigi96): verify format + for _, entry := range opts.Share { + // share = xxxx:[r|w] + shareIDPerm := strings.Split(entry, ":") + shareID, perm := shareIDPerm[0], shareIDPerm[1] + scope, err := getPublicShareScope(ctx, client, shareID, perm) + if err != nil { + return nil, err + } + scopeList = append(scopeList, scope) + } + fallthrough + case len(opts.Path) != 0: + // TODO(gmgigi96): verify format + for _, entry := range opts.Path { + // path = /home/a/b:[r|w] + pathPerm := strings.Split(entry, ":") + path, perm := pathPerm[0], pathPerm[1] + scope, err := getPathScope(ctx, client, path, perm) + if err != nil { + return nil, err + } + scopeList = append(scopeList, scope) + } + fallthrough + default: + return mergeListScopeIntoMap(scopeList), nil } +} - return nil, nil +func mergeListScopeIntoMap(scopeList []map[string]*authpb.Scope) map[string]*authpb.Scope { + merged := make(map[string]*authpb.Scope) + for _, scope := range scopeList { + for k, v := range scope { + merged[k] = v + } + } + return merged } func getPublicShareScope(ctx context.Context, client gateway.GatewayAPIClient, shareID, perm string) (map[string]*authpb.Scope, error) { @@ -214,7 +250,7 @@ func parsePermission(perm string) (authpb.Role, error) { } func checkOpts(opts *appTokenCreateOpts) error { - if opts.Share == "" && opts.Path == "" && !opts.Unlimited { + if len(opts.Share) == 0 && len(opts.Path) == 0 && !opts.Unlimited { return errtypes.BadRequest("specify a token scope") } return nil diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index 76eafd8881..0091265289 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -140,8 +140,9 @@ func prettyFormatScope(scopeMap map[string]*authpv.Scope) (string, error) { return "", err } scopeFormatted.WriteString(scopeStr) + scopeFormatted.WriteString(", ") } - return scopeFormatted.String(), nil + return scopeFormatted.String()[:scopeFormatted.Len()-2], nil } // Filter the list of app password, based on the option selected by the user From c383c08a091b0ce8c4d754cacc59a063bfd0c1e2 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Mon, 31 May 2021 17:12:00 +0200 Subject: [PATCH 20/23] cli: changes according to review --- cmd/reva/app-tokens-create.go | 44 +++++------ cmd/reva/app-tokens-list.go | 137 +++------------------------------- cmd/reva/app-tokens-remove.go | 4 +- pkg/auth/scope/scope.go | 37 --------- pkg/auth/scope/utils.go | 62 +++++++++++++++ 5 files changed, 94 insertions(+), 190 deletions(-) create mode 100644 pkg/auth/scope/utils.go diff --git a/cmd/reva/app-tokens-create.go b/cmd/reva/app-tokens-create.go index 1e2d4fb31e..f23295f5e7 100644 --- a/cmd/reva/app-tokens-create.go +++ b/cmd/reva/app-tokens-create.go @@ -20,9 +20,7 @@ package main import ( "context" - "encoding/gob" "io" - "reflect" "strings" "time" @@ -56,29 +54,34 @@ func (ss *stringSlice) String() string { return strings.Join([]string(*ss), ",") } -var createOpts *appTokenCreateOpts = &appTokenCreateOpts{} - const layoutTime = "2006-01-02" func appTokensCreateCommand() *command { - cmd := newCommand("token-create") + cmd := newCommand("app-tokens-create") cmd.Description = func() string { return "create a new application tokens" } cmd.Usage = func() string { return "Usage: token-create" } - cmd.StringVar(&createOpts.Label, "label", "", "set a label") - cmd.StringVar(&createOpts.Expiration, "expiration", "", "set expiration time (format )") - // TODO(gmgigi96): add support for multiple paths and shares for the same token - cmd.Var(&createOpts.Path, "path", "create a token for a file (format path:[r|w]). It is possible specify multiple times this flag") - cmd.Var(&createOpts.Share, "share", "create a token for a share (format shareid:[r|w]). It is possible specify multiple times this flags") - cmd.BoolVar(&createOpts.Unlimited, "all", false, "create a token with an unlimited scope") + var path, share stringSlice + label := cmd.String("label", "", "set a label") + expiration := cmd.String("expiration", "", "set expiration time (format )") + cmd.Var(&path, "path", "create a token for a file (format path:[r|w]). It is possible specify this flag multiple times") + cmd.Var(&share, "share", "create a token for a share (format shareid:[r|w]). It is possible specify this flag multiple times") + unlimited := cmd.Bool("all", false, "create a token with an unlimited scope") cmd.ResetFlags = func() { - s := reflect.ValueOf(createOpts).Elem() - s.Set(reflect.Zero(s.Type())) + path, share, label, expiration, unlimited = nil, nil, nil, nil, nil } cmd.Action = func(w ...io.Writer) error { + createOpts := &appTokenCreateOpts{ + Expiration: *expiration, + Label: *label, + Path: path, + Share: share, + Unlimited: *unlimited, + } + err := checkOpts(createOpts) if err != nil { return err @@ -121,18 +124,9 @@ func appTokensCreateCommand() *command { return formatError(generateAppPasswordResponse.Status) } - pw := generateAppPasswordResponse.AppPassword - - if len(w) == 0 { - err = printTableAppPasswords([]*authapp.AppPassword{pw}, true) - if err != nil { - return err - } - } else { - enc := gob.NewEncoder(w[0]) - if err := enc.Encode(pw); err != nil { - return err - } + err = printTableAppPasswords([]*authapp.AppPassword{generateAppPasswordResponse.AppPassword}) + if err != nil { + return err } return nil diff --git a/cmd/reva/app-tokens-list.go b/cmd/reva/app-tokens-list.go index 0091265289..62605d434f 100644 --- a/cmd/reva/app-tokens-list.go +++ b/cmd/reva/app-tokens-list.go @@ -19,10 +19,8 @@ package main import ( - "encoding/gob" "io" "os" - "reflect" "strings" "time" @@ -34,31 +32,10 @@ import ( "github.com/jedib0t/go-pretty/table" ) -type appTokenListOpts struct { - Long bool - All bool - OnlyExpired bool - ApplicationFilter string - Label string -} - -var listOpts *appTokenListOpts = &appTokenListOpts{} - func appTokensListCommand() *command { - cmd := newCommand("token-list") + cmd := newCommand("app-tokens-list") cmd.Description = func() string { return "list all the application tokens" } - cmd.Usage = func() string { return "Usage: token-list [-flags]" } - - cmd.BoolVar(&listOpts.Long, "long", false, "long listing") - cmd.BoolVar(&listOpts.All, "all", false, "print all tokens, also the expired") - cmd.BoolVar(&listOpts.OnlyExpired, "expired", false, "print only expired token") - cmd.StringVar(&listOpts.ApplicationFilter, "sope", "", "filter by scope") - cmd.StringVar(&listOpts.Label, "label", "", "filter by label name") - - cmd.ResetFlags = func() { - s := reflect.ValueOf(listOpts).Elem() - s.Set(reflect.Zero(s.Type())) - } + cmd.Usage = func() string { return "Usage: token-list" } cmd.Action = func(w ...io.Writer) error { @@ -78,47 +55,30 @@ func appTokensListCommand() *command { return formatError(listResponse.Status) } - listPw := filterAppPasswords(listResponse.AppPasswords, listOpts) - - if len(w) == 0 { - err = printTableAppPasswords(listPw, listOpts.Long) - if err != nil { - return err - } - } else { - enc := gob.NewEncoder(w[0]) - if err := enc.Encode(listPw); err != nil { - return err - } + err = printTableAppPasswords(listResponse.AppPasswords) + if err != nil { + return err } + return nil } return cmd } -func printTableAppPasswords(listPw []*applications.AppPassword, long bool) error { - shortHeader := table.Row{"Token", "Label", "Scope", "Expiration"} - longHeader := table.Row{"Token", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} +func printTableAppPasswords(listPw []*applications.AppPassword) error { + header := table.Row{"Token", "Scope", "Label", "Expiration", "Creation Time", "Last Used Time"} t := table.NewWriter() t.SetOutputMirror(os.Stdout) - if long { - t.AppendHeader(longHeader) - } else { - t.AppendHeader(shortHeader) - } + t.AppendHeader(header) for _, pw := range listPw { scopeFormatted, err := prettyFormatScope(pw.TokenScope) if err != nil { return err } - if long { - t.AppendRow(table.Row{pw.Password, scopeFormatted, pw.Label, formatTime(pw.Expiration), formatTime(pw.Ctime), formatTime(pw.Utime)}) - } else { - t.AppendRow(table.Row{pw.Password, pw.Label, scopeFormatted, formatTime(pw.Expiration)}) - } + t.AppendRow(table.Row{pw.Password, scopeFormatted, pw.Label, formatTime(pw.Expiration), formatTime(pw.Ctime), formatTime(pw.Utime)}) } t.Render() @@ -144,80 +104,3 @@ func prettyFormatScope(scopeMap map[string]*authpv.Scope) (string, error) { } return scopeFormatted.String()[:scopeFormatted.Len()-2], nil } - -// Filter the list of app password, based on the option selected by the user -func filterAppPasswords(listPw []*applications.AppPassword, opts *appTokenListOpts) (filtered []*applications.AppPassword) { - var filters filters - - if opts.OnlyExpired { - filters = append(filters, &filterByExpired{}) - } else { - filters = append(filters, &filterByNotExpired{}) - } - if opts.ApplicationFilter != "" { - filters = append(filters, &filterByApplicationName{name: opts.ApplicationFilter}) - } - if opts.Label != "" { - filters = append(filters, &filterByLabel{label: opts.Label}) - } - if opts.All { - // discard all the filters - filters = []filter{&filterByNone{}} - } - - for _, pw := range listPw { - if filters.in(pw) { - filtered = append(filtered, pw) - } - } - return -} - -type filter interface { - in(*applications.AppPassword) bool -} - -type filterByApplicationName struct { - name string -} -type filterByNone struct{} -type filterByExpired struct{} -type filterByNotExpired struct{} -type filterByLabel struct { - label string -} -type filters []filter - -func (f *filterByApplicationName) in(pw *applications.AppPassword) bool { - for app := range pw.TokenScope { - if app == f.name { - return true - } - } - return false -} - -func (f *filterByNone) in(pw *applications.AppPassword) bool { - return true -} - -func (f *filterByExpired) in(pw *applications.AppPassword) bool { - return pw.Expiration != nil && pw.Expiration.Seconds <= uint64(time.Now().Unix()) -} - -func (f *filterByNotExpired) in(pw *applications.AppPassword) bool { - return !(&filterByExpired{}).in(pw) -} - -func (f *filterByLabel) in(pw *applications.AppPassword) bool { - return f.label != "" && pw.Label == f.label -} - -func (f filters) in(pw *applications.AppPassword) bool { - for _, filter := range f { - if !filter.in(pw) { - return false - } - } - return true -} diff --git a/cmd/reva/app-tokens-remove.go b/cmd/reva/app-tokens-remove.go index 6fdf63fd2a..1fc0fcf8ea 100644 --- a/cmd/reva/app-tokens-remove.go +++ b/cmd/reva/app-tokens-remove.go @@ -19,6 +19,7 @@ package main import ( + "fmt" "io" applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" @@ -27,7 +28,7 @@ import ( ) func appTokensRemoveCommand() *command { - cmd := newCommand("token-remove") + cmd := newCommand("app-tokens-remove") cmd.Description = func() string { return "remove an application token" } cmd.Usage = func() string { return "Usage: token-remove " } @@ -56,6 +57,7 @@ func appTokensRemoveCommand() *command { return formatError(response.Status) } + fmt.Println("OK") return nil } diff --git a/pkg/auth/scope/scope.go b/pkg/auth/scope/scope.go index d1943f9053..35bdb29cfc 100644 --- a/pkg/auth/scope/scope.go +++ b/pkg/auth/scope/scope.go @@ -19,14 +19,9 @@ package scope import ( - "fmt" "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/utils" ) // Verifier is the function signature which every scope verifier should implement. @@ -56,35 +51,3 @@ func VerifyScope(scopeMap map[string]*authpb.Scope, resource interface{}) (bool, } return false, nil } - -// FormatScope create a pretty print of the scope -func FormatScope(scopeType string, scope *authpb.Scope) (string, error) { - // TODO(gmgigi96): check decoder type - switch { - case strings.HasPrefix(scopeType, "user"): - // user scope - var ref provider.Reference - err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &ref) - if err != nil { - return "", err - } - return fmt.Sprintf("%s %s", ref.String(), scope.Role.String()), nil - case strings.HasPrefix(scopeType, "publicshare"): - // public share - var pShare link.PublicShare - err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &pShare) - if err != nil { - return "", err - } - return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil - case strings.HasPrefix(scopeType, "resourceinfo"): - var resInfo provider.ResourceInfo - err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &resInfo) - if err != nil { - return "", err - } - return fmt.Sprintf("path:\"%s\" %s", resInfo.Path, scope.Role.String()), nil - default: - return "", errtypes.NotSupported("scope not yet supported") - } -} diff --git a/pkg/auth/scope/utils.go b/pkg/auth/scope/utils.go new file mode 100644 index 0000000000..a442db9901 --- /dev/null +++ b/pkg/auth/scope/utils.go @@ -0,0 +1,62 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package scope + +import ( + "fmt" + "strings" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" +) + +// FormatScope create a pretty print of the scope +func FormatScope(scopeType string, scope *authpb.Scope) (string, error) { + // TODO(gmgigi96): check decoder type + switch { + case strings.HasPrefix(scopeType, "user"): + // user scope + var ref provider.Reference + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &ref) + if err != nil { + return "", err + } + return fmt.Sprintf("%s %s", ref.String(), scope.Role.String()), nil + case strings.HasPrefix(scopeType, "publicshare"): + // public share + var pShare link.PublicShare + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &pShare) + if err != nil { + return "", err + } + return fmt.Sprintf("share:\"%s\" %s", pShare.Id.OpaqueId, scope.Role.String()), nil + case strings.HasPrefix(scopeType, "resourceinfo"): + var resInfo provider.ResourceInfo + err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &resInfo) + if err != nil { + return "", err + } + return fmt.Sprintf("path:\"%s\" %s", resInfo.Path, scope.Role.String()), nil + default: + return "", errtypes.NotSupported("scope not yet supported") + } +} From 6e99b3919f04f98103648894351f581aa100ef34 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Mon, 7 Jun 2021 15:55:10 +0200 Subject: [PATCH 21/23] appauth: add appauth manager in loader --- pkg/auth/manager/loader/loader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/auth/manager/loader/loader.go b/pkg/auth/manager/loader/loader.go index ad693c5b6c..adbc173848 100644 --- a/pkg/auth/manager/loader/loader.go +++ b/pkg/auth/manager/loader/loader.go @@ -20,6 +20,7 @@ package loader import ( // Load core authentication managers. + _ "github.com/cs3org/reva/pkg/auth/manager/appauth" _ "github.com/cs3org/reva/pkg/auth/manager/demo" _ "github.com/cs3org/reva/pkg/auth/manager/impersonator" _ "github.com/cs3org/reva/pkg/auth/manager/json" From f9e845fd38887776ce2fa3762fa6239b7cc7cbc0 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Mon, 7 Jun 2021 15:55:24 +0200 Subject: [PATCH 22/23] appauth: add example --- examples/storage-references/applicationauth.toml | 11 +++++++++++ examples/storage-references/gateway.toml | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 examples/storage-references/applicationauth.toml diff --git a/examples/storage-references/applicationauth.toml b/examples/storage-references/applicationauth.toml new file mode 100644 index 0000000000..8da1c4a708 --- /dev/null +++ b/examples/storage-references/applicationauth.toml @@ -0,0 +1,11 @@ +[grpc] +address = "0.0.0.0:15000" + +[grpc.services.applicationauth] +driver = "json" + +[grpc.services.authprovider] +auth_manager = "appauth" + +[grpc.services.authprovider.auth_managers.appauth] +gateway_addr = "localhost:19000" \ No newline at end of file diff --git a/examples/storage-references/gateway.toml b/examples/storage-references/gateway.toml index 402935c9ee..b1502581d9 100644 --- a/examples/storage-references/gateway.toml +++ b/examples/storage-references/gateway.toml @@ -14,10 +14,12 @@ home_provider = "/home" [grpc.services.authprovider] [grpc.services.authregistry] +[grpc.services.applicationauth] [grpc.services.authregistry.drivers.static.rules] basic = "localhost:19000" publicshares = "localhost:16000" +appauth = "localhost:15000" [grpc.services.userprovider] [grpc.services.usershareprovider] From 913fae7a43a2e9a9815946335ecc6fc7196bd0ba Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte Date: Mon, 7 Jun 2021 19:07:17 +0200 Subject: [PATCH 23/23] appatuh: removed unnecessary service in example --- examples/storage-references/applicationauth.toml | 3 --- examples/storage-references/gateway.toml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/storage-references/applicationauth.toml b/examples/storage-references/applicationauth.toml index 8da1c4a708..bca8cd899e 100644 --- a/examples/storage-references/applicationauth.toml +++ b/examples/storage-references/applicationauth.toml @@ -1,9 +1,6 @@ [grpc] address = "0.0.0.0:15000" -[grpc.services.applicationauth] -driver = "json" - [grpc.services.authprovider] auth_manager = "appauth" diff --git a/examples/storage-references/gateway.toml b/examples/storage-references/gateway.toml index b1502581d9..a477512a5b 100644 --- a/examples/storage-references/gateway.toml +++ b/examples/storage-references/gateway.toml @@ -14,13 +14,13 @@ home_provider = "/home" [grpc.services.authprovider] [grpc.services.authregistry] -[grpc.services.applicationauth] [grpc.services.authregistry.drivers.static.rules] basic = "localhost:19000" publicshares = "localhost:16000" appauth = "localhost:15000" +[grpc.services.applicationauth] [grpc.services.userprovider] [grpc.services.usershareprovider] [grpc.services.groupprovider]