Skip to content

Commit

Permalink
feat(cmd): add access command
Browse files Browse the repository at this point in the history
and work out a bunch of details for provisioning tokens
  • Loading branch information
b5 committed Mar 4, 2021
1 parent 3be7af2 commit 1c56680
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 4 deletions.
79 changes: 79 additions & 0 deletions cmd/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cmd

import (
"context"

"github.com/qri-io/ioes"
"github.com/qri-io/qri/lib"
"github.com/spf13/cobra"
)

// NewAccessCommand creates a new `qri apply` cobra command for applying transformations
func NewAccessCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
o := &AccessOptions{IOStreams: ioStreams}
cmd := &cobra.Command{
Use: "access",
Short: "manage user permissions",
Long: ``,
Example: ``,
Annotations: map[string]string{
"group": "other",
},
}

tokenCmd := &cobra.Command{
Use: "token",
Short: "create an access token",
Long: `
token creates a JSON Web Token (JWT) that authenticates the given user.
Constructing an access token requires a private key that backs the given user.
In the course of normal operation you shouldn't need this command, It's mainly
here for crafting API requests in external progrmas`[1:],
Example: `
# create an access token to authenticate yourself else where:
$ qri access token --for me
`[1:],
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(f, args); err != nil {
return err
}
ctx := context.TODO()
return o.CreateAccessToken(ctx)
},
}
tokenCmd.Flags().StringVar(&o.GranteeUsername, "for", "", "user to create access token for")
tokenCmd.MarkFlagRequired("for")

cmd.AddCommand(tokenCmd)
return cmd
}

// AccessOptions encapsulates state for the apply command
type AccessOptions struct {
ioes.IOStreams
Instance *lib.Instance

GranteeUsername string
}

// Complete adds any missing configuration that can only be added just before calling Run
func (o *AccessOptions) Complete(f Factory, args []string) (err error) {
o.Instance = f.Instance()
return nil
}

// CreateAccessToken constructs an access token suitable for making
// authenticated requests
func (o *AccessOptions) CreateAccessToken(ctx context.Context) error {
p := &lib.CreateAuthTokenParams{
GranteeUsername: o.GranteeUsername,
}
token, err := o.Instance.Access().CreateAuthToken(ctx, p)
if err != nil {
return err
}

printInfo(o.Out, token)
return nil
}
13 changes: 13 additions & 0 deletions cmd/access_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cmd

import (
"testing"
)

func TestAccessCreateToken(t *testing.T) {
run := NewTestRunner(t, "peer", "cmd_test_create_access_token")
defer run.Delete()

run.MustExecCombinedOutErr(t, "qri access token --for me")
run.MustExecCombinedOutErr(t, "qri access token --for peer")
}
1 change: 1 addition & 0 deletions cmd/qri.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ https://github.com/qri-io/qri/issues`,
cmd.PersistentFlags().BoolVarP(&opt.LogAll, "log-all", "", false, "log all activity")

cmd.AddCommand(
NewAccessCommand(opt, ioStreams),
NewApplyCommand(opt, ioStreams),
NewAutocompleteCommand(opt, ioStreams),
NewCheckoutCommand(opt, ioStreams),
Expand Down
5 changes: 4 additions & 1 deletion lib/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ func (accessImpl) CreateAuthToken(scp scope, p *CreateAuthTokenParams) (string,
if grantee, err = scp.Profiles().GetProfile(id); err != nil {
return "", err
}
} else if p.GranteeUsername != "" {
} else if p.GranteeUsername == "me" {
// TODO(b5): this should be scp.ActiveUser()
grantee = scp.Profiles().Owner()
} else {
if grantee, err = profile.ResolveUsername(scp.Profiles(), p.GranteeUsername); err != nil {
return "", err
}
Expand Down
10 changes: 8 additions & 2 deletions profile/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,6 @@ func (m *MemStore) PeerProfile(id peer.ID) (*Profile, error) {
m.Lock()
defer m.Unlock()

// str := fmt.Sprintf("/ipfs/%s", id.Pretty())

for _, profile := range m.store {
for _, pid := range profile.PeerIDs {
if id == pid {
Expand Down Expand Up @@ -450,6 +448,10 @@ func (r *LocalStore) GetProfile(id ID) (*Profile, error) {
r.Lock()
defer r.Unlock()

if id == r.owner.ID {
return r.owner, nil
}

ps, err := r.profiles()
if err != nil {
return nil, err
Expand Down Expand Up @@ -482,6 +484,10 @@ func (r *LocalStore) ProfilesForUsername(username string) ([]*Profile, error) {
}

var res []*Profile
if username == r.owner.Peername {
res = append(res, r.owner)
}

for id, p := range ps {
if p.Peername == username {
pro := &Profile{}
Expand Down
2 changes: 1 addition & 1 deletion profile/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestMemStoreGetOwner(t *testing.T) {
t.Error("getting owner profile must return profile with private key populated")
}

if diff := cmp.Diff(owner, pro, cmpopts.IgnoreUnexported(Profile{}, crypto.RsaPrivateKey{}, crypto.ECDSAPrivateKey{})); diff != "" {
if diff := cmp.Diff(owner, pro, cmpopts.IgnoreUnexported(Profile{}, crypto.RsaPublicKey{}, crypto.RsaPrivateKey{}, crypto.ECDSAPublicKey{}, crypto.ECDSAPrivateKey{})); diff != "" {
t.Errorf("get owner mismatch. (-want +got):\n%s", diff)
}
}
Expand Down

0 comments on commit 1c56680

Please sign in to comment.