Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application passwords CLI commands #1743

Merged
merged 23 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1793232
cli: token remove
gmgigi96 May 26, 2021
14f42ea
cli: tokens list
gmgigi96 May 26, 2021
84e8753
cli: token create
gmgigi96 May 26, 2021
3e8eb01
cli: created commands in main file
gmgigi96 May 26, 2021
6628f6c
cli: create token scope
gmgigi96 May 27, 2021
f7c65a6
cli: error check in token creation
gmgigi96 May 27, 2021
fa9c105
cli: reset flags and improved filters in tokens-list cmd
gmgigi96 May 27, 2021
d17d0e3
cli: better formatted values in token list
gmgigi96 May 28, 2021
acf6788
refactoring: moved FormatScope in scope
gmgigi96 May 28, 2021
6e7ad73
refactoring: print table of app passwords in a separate function
gmgigi96 May 28, 2021
1d4bfa2
cli: better print of the new token
gmgigi96 May 28, 2021
5b651c9
appauth cli: add changelog
gmgigi96 May 28, 2021
b5d3253
go mod download
gmgigi96 May 28, 2021
3d5fe90
appauth: added default value for token strength and fix bug in token …
gmgigi96 May 28, 2021
5de0b17
add comments and remove unnecessary exports
gmgigi96 May 28, 2021
f5b42b8
appauth: save hashed pw instead of plain text pw
gmgigi96 May 28, 2021
f0f26d4
appauth: change test with hashed pw saved in json
gmgigi96 May 28, 2021
8651667
scope: add pretty print to path scope
gmgigi96 May 28, 2021
7390f5e
cli: add support for multiple path and share in the same scope
gmgigi96 May 28, 2021
c383c08
cli: changes according to review
gmgigi96 May 31, 2021
6e99b39
appauth: add appauth manager in loader
gmgigi96 Jun 7, 2021
f9e845f
appauth: add example
gmgigi96 Jun 7, 2021
913fae7
appatuh: removed unnecessary service in example
gmgigi96 Jun 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/unreleased/app-passwords-cli.md
Original file line number Diff line number Diff line change
@@ -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
251 changes: 251 additions & 0 deletions cmd/reva/app-tokens-create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// 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 (
"context"
"io"
"strings"
"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 {
Expiration string
Label 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), ",")
}

const layoutTime = "2006-01-02"

func appTokensCreateCommand() *command {
cmd := newCommand("app-tokens-create")
cmd.Description = func() string { return "create a new application tokens" }
cmd.Usage = func() string { return "Usage: token-create" }

var path, share stringSlice
label := cmd.String("label", "", "set a label")
expiration := cmd.String("expiration", "", "set expiration time (format <yyyy-mm-dd>)")
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() {
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
}

client, err := getClient()
if err != nil {
return err
}

ctx := getAuthContext()

scope, err := getScope(ctx, client, createOpts)
if err != nil {
return err
}

// parse eventually expiration time
var expiration *types.Timestamp
if createOpts.Expiration != "" {
exp, err := time.Parse(layoutTime, createOpts.Expiration)
if err != nil {
return err
}
expiration = &types.Timestamp{
Seconds: uint64(exp.Unix()),
}
}

generateAppPasswordResponse, err := client.GenerateAppPassword(ctx, &authapp.GenerateAppPasswordRequest{
Expiration: expiration,
Label: createOpts.Label,
TokenScope: scope,
})

if err != nil {
return err
}
if generateAppPasswordResponse.Status.Code != rpc.Code_CODE_OK {
return formatError(generateAppPasswordResponse.Status)
}

err = printTableAppPasswords([]*authapp.AppPassword{generateAppPasswordResponse.AppPassword})
if err != nil {
return err
}

return nil
}

return cmd
}

func getScope(ctx context.Context, client gateway.GatewayAPIClient, opts *appTokenCreateOpts) (map[string]*authpb.Scope, error) {
var scopeList []map[string]*authpb.Scope
switch {
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
}
}

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) {
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")
}
}

func checkOpts(opts *appTokenCreateOpts) error {
if len(opts.Share) == 0 && len(opts.Path) == 0 && !opts.Unlimited {
return errtypes.BadRequest("specify a token scope")
}
return nil
}
106 changes: 106 additions & 0 deletions cmd/reva/app-tokens-list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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"
"os"
"strings"
"time"

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"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
scope "github.com/cs3org/reva/pkg/auth/scope"
"github.com/jedib0t/go-pretty/table"
)

func appTokensListCommand() *command {
cmd := newCommand("app-tokens-list")
cmd.Description = func() string { return "list all the application tokens" }
cmd.Usage = func() string { return "Usage: token-list" }

cmd.Action = func(w ...io.Writer) error {

client, err := getClient()
if err != nil {
return err
}

ctx := getAuthContext()
listResponse, err := client.ListAppPasswords(ctx, &applications.ListAppPasswordsRequest{})

if err != nil {
return err
}

if listResponse.Status.Code != rpc.Code_CODE_OK {
return formatError(listResponse.Status)
}

err = printTableAppPasswords(listResponse.AppPasswords)
if err != nil {
return err
}

return nil
}
return cmd
}

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)

t.AppendHeader(header)

for _, pw := range listPw {
scopeFormatted, err := prettyFormatScope(pw.TokenScope)
if err != nil {
return err
}
t.AppendRow(table.Row{pw.Password, scopeFormatted, pw.Label, formatTime(pw.Expiration), formatTime(pw.Ctime), formatTime(pw.Utime)})
}

t.Render()
return nil
}

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 scType, sc := range scopeMap {
scopeStr, err := scope.FormatScope(scType, sc)
if err != nil {
return "", err
}
scopeFormatted.WriteString(scopeStr)
scopeFormatted.WriteString(", ")
}
return scopeFormatted.String()[:scopeFormatted.Len()-2], nil
}
Loading