diff --git a/cmd/access.go b/cmd/access.go new file mode 100644 index 000000000..72dfeb6ab --- /dev/null +++ b/cmd/access.go @@ -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 +} diff --git a/cmd/access_test.go b/cmd/access_test.go new file mode 100644 index 000000000..2247bf770 --- /dev/null +++ b/cmd/access_test.go @@ -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") +} diff --git a/cmd/qri.go b/cmd/qri.go index f33f51d3f..6d57310ec 100644 --- a/cmd/qri.go +++ b/cmd/qri.go @@ -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), diff --git a/lib/access.go b/lib/access.go index e76fb5056..66a393c69 100644 --- a/lib/access.go +++ b/lib/access.go @@ -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 } diff --git a/profile/store.go b/profile/store.go index c2fb7cebb..c7753240f 100644 --- a/profile/store.go +++ b/profile/store.go @@ -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 { @@ -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 @@ -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{} diff --git a/profile/store_test.go b/profile/store_test.go index ba9bfa261..bb734bd16 100644 --- a/profile/store_test.go +++ b/profile/store_test.go @@ -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) } }