github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/client/cmd_team_settings.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/keybase/cli"
     8  	"github.com/keybase/client/go/libcmdline"
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/protocol/chat1"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  	"golang.org/x/net/context"
    13  )
    14  
    15  type CmdTeamSettings struct {
    16  	libkb.Contextified
    17  
    18  	Team   keybase1.TeamName
    19  	teamID keybase1.TeamID
    20  
    21  	// These fields are non-zero valued when their action is requested
    22  	Description           *string
    23  	WelcomeMessage        *string
    24  	ResetWelcomeMessage   *bool
    25  	JoinAsRole            *keybase1.TeamRole
    26  	ProfilePromote        *bool
    27  	AllowProfilePromote   *bool
    28  	Showcase              *bool
    29  	DisableAccessRequests *bool
    30  }
    31  
    32  func newCmdTeamSettings(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
    33  	return cli.Command{
    34  		Name:         "settings",
    35  		ArgumentHelp: "<team name>",
    36  		Usage:        "Edit team settings.",
    37  		Examples: `
    38  Review team settings:
    39      keybase team settings acme
    40  Open a team so anyone can join as a reader:
    41      keybase team settings acme --open-team=reader
    42  Showcase a team publicly:
    43      keybase team settings acme --showcase=yes
    44  Promote a team on your profile:
    45      keybase team settings acme --profile-promote=yes
    46  Set a description for the team to show if promoted:
    47      keybase team settings acme --description="Rocket-Powered Products"
    48  Clear the team description:
    49      keybase team settings acme --description=""
    50  `,
    51  		Action: func(c *cli.Context) {
    52  			cmd := NewCmdTeamSettingsRunner(g)
    53  			cl.ChooseCommand(cmd, "settings", c)
    54  		},
    55  		Flags: []cli.Flag{
    56  			// Many of these are StringFlag instead of BoolFlag because BoolFlag is displeasing.
    57  			// For example `keybase team settings teamname --bool-flag false` sets the flag to true.
    58  
    59  			cli.BoolFlag{
    60  				Name:  "p, print",
    61  				Usage: "Print all your team settings",
    62  			},
    63  			cli.StringFlag{
    64  				Name:  "description",
    65  				Usage: "Set the team description",
    66  			},
    67  			cli.StringFlag{
    68  				Name:  "open-team",
    69  				Usage: "[reader|writer|off] Set whether anyone can join without being invited and the role they become",
    70  			},
    71  			cli.StringFlag{
    72  				Name:  "profile-promote",
    73  				Usage: "[yes|no] Set whether your own profile should promote this team and its description",
    74  			},
    75  			cli.StringFlag{
    76  				Name:  "allow-profile-promote",
    77  				Usage: "[yes|no] Set whether non-admins are allowed to promote this team and its description on their profiles",
    78  			},
    79  			cli.StringFlag{
    80  				Name:  "showcase",
    81  				Usage: "[yes|no] Set whether to promote this team and its description on keybase.io/popular-teams",
    82  			},
    83  			cli.StringFlag{
    84  				Name:  "disable-access-requests",
    85  				Usage: "[yes|no] Set whether it should be possible to access request to this team",
    86  			},
    87  			// cli.StringFlag{
    88  			// 	Name:  "welcome-message",
    89  			// 	Usage: "Set a welcome message for new team members. Empty string for no welcome message.",
    90  			// },
    91  			// cli.BoolFlag{
    92  			// 	Name:  "reset-welcome-message",
    93  			// 	Usage: "Reset the welcome message to the default.",
    94  			// },
    95  		},
    96  	}
    97  }
    98  
    99  func NewCmdTeamSettingsRunner(g *libkb.GlobalContext) *CmdTeamSettings {
   100  	return &CmdTeamSettings{Contextified: libkb.NewContextified(g)}
   101  }
   102  
   103  func (c *CmdTeamSettings) ParseArgv(ctx *cli.Context) (err error) {
   104  	c.Team, err = ParseOneTeamNameK1(ctx)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	var exclusiveActions []string
   110  
   111  	if ctx.IsSet("description") {
   112  		exclusiveActions = append(exclusiveActions, "description")
   113  		desc := ctx.String("description")
   114  		c.Description = &desc
   115  	}
   116  
   117  	if ctx.IsSet("open-team") {
   118  		exclusiveActions = append(exclusiveActions, "open-team")
   119  
   120  		role := keybase1.TeamRole_NONE
   121  		val := ctx.String("open-team")
   122  		switch val {
   123  		case "reader":
   124  			role = keybase1.TeamRole_READER
   125  		case "writer":
   126  			role = keybase1.TeamRole_WRITER
   127  		default:
   128  			open, err := cli.ParseBoolFriendly(val)
   129  			if err != nil || open {
   130  				return fmt.Errorf("open-team must be one of [reader|writer|off]")
   131  			}
   132  		}
   133  		c.JoinAsRole = &role
   134  	}
   135  
   136  	if ctx.IsSet("profile-promote") {
   137  		exclusiveActions = append(exclusiveActions, "profile-promote")
   138  		val, err := ctx.BoolStrict("profile-promote")
   139  		if err != nil {
   140  			return err
   141  		}
   142  		c.ProfilePromote = &val
   143  	}
   144  
   145  	if ctx.IsSet("allow-profile-promote") {
   146  		exclusiveActions = append(exclusiveActions, "allow-profile-promote")
   147  		val, err := ctx.BoolStrict("allow-profile-promote")
   148  		if err != nil {
   149  			return err
   150  		}
   151  		c.AllowProfilePromote = &val
   152  	}
   153  
   154  	if ctx.IsSet("showcase") {
   155  		exclusiveActions = append(exclusiveActions, "showcase")
   156  		val, err := ctx.BoolStrict("showcase")
   157  		if err != nil {
   158  			return err
   159  		}
   160  		c.Showcase = &val
   161  	}
   162  
   163  	if ctx.IsSet("disable-access-requests") {
   164  		exclusiveActions = append(exclusiveActions, "disable-access-requests")
   165  		val, err := ctx.BoolStrict("disable-access-requests")
   166  		if err != nil {
   167  			return err
   168  		}
   169  		c.DisableAccessRequests = &val
   170  	}
   171  
   172  	if ctx.IsSet("welcome-message") {
   173  		exclusiveActions = append(exclusiveActions, "welcome-message")
   174  		welcomeMessage := ctx.String("welcome-message")
   175  		c.WelcomeMessage = &welcomeMessage
   176  	}
   177  
   178  	if ctx.IsSet("reset-welcome-message") {
   179  		exclusiveActions = append(exclusiveActions, "reset-welcome-message")
   180  		resetWelcomeMessage := ctx.Bool("reset-welcome-message")
   181  		if !resetWelcomeMessage {
   182  			return fmt.Errorf("cannot pass a false value to --reset-welcome-message")
   183  		}
   184  		c.ResetWelcomeMessage = &resetWelcomeMessage
   185  	}
   186  
   187  	if len(exclusiveActions) > 1 {
   188  		return fmt.Errorf("only one of these actions a time: %v", strings.Join(exclusiveActions, ", "))
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func (c *CmdTeamSettings) Run() error {
   195  	ctx, ctxCancel := context.WithCancel(context.TODO())
   196  	defer ctxCancel()
   197  	ctx = libkb.WithLogTag(ctx, "CTS")
   198  
   199  	cli, err := GetTeamsClient(c.G())
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	teamID, err := cli.GetTeamID(context.Background(), c.Team.String())
   205  	if err != nil {
   206  		return err
   207  	}
   208  	c.teamID = teamID
   209  
   210  	if c.Description != nil {
   211  		err = c.setDescription(ctx, cli, *c.Description)
   212  		if err != nil {
   213  			return err
   214  		}
   215  	}
   216  
   217  	if c.JoinAsRole != nil {
   218  		err = c.setOpenness(ctx, cli, *c.JoinAsRole)
   219  		if err != nil {
   220  			return err
   221  		}
   222  	}
   223  
   224  	if c.ProfilePromote != nil {
   225  		err = c.setProfilePromote(ctx, cli, *c.ProfilePromote)
   226  		if err != nil {
   227  			return err
   228  		}
   229  	}
   230  
   231  	if c.AllowProfilePromote != nil {
   232  		err = c.setAllowProfilePromote(ctx, cli, *c.AllowProfilePromote)
   233  		if err != nil {
   234  			return err
   235  		}
   236  	}
   237  
   238  	if c.Showcase != nil {
   239  		err = c.setShowcase(ctx, cli, *c.Showcase)
   240  		if err != nil {
   241  			return err
   242  		}
   243  	}
   244  
   245  	if c.DisableAccessRequests != nil {
   246  		err = c.setDisableAccessRequests(ctx, cli, *c.DisableAccessRequests)
   247  		if err != nil {
   248  			return err
   249  		}
   250  	}
   251  
   252  	if c.WelcomeMessage != nil {
   253  		err = c.setWelcomeMessage(ctx, *c.WelcomeMessage)
   254  		if err != nil {
   255  			return err
   256  		}
   257  	}
   258  
   259  	if c.ResetWelcomeMessage != nil {
   260  		err = c.resetWelcomeMessage(ctx)
   261  		if err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	err = c.printCurrentSettings(ctx, cli)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	return nil
   272  }
   273  
   274  func (c *CmdTeamSettings) setDescription(ctx context.Context, cli keybase1.TeamsClient, desc string) error {
   275  	return cli.SetTeamShowcase(ctx, keybase1.SetTeamShowcaseArg{
   276  		TeamID:            c.teamID,
   277  		IsShowcased:       nil,
   278  		Description:       &desc,
   279  		AnyMemberShowcase: nil,
   280  	})
   281  }
   282  
   283  func (c *CmdTeamSettings) setOpenness(ctx context.Context, cli keybase1.TeamsClient, joinAsRole keybase1.TeamRole) error {
   284  	dui := c.G().UI.GetTerminalUI()
   285  
   286  	open := joinAsRole != keybase1.TeamRole_NONE
   287  
   288  	arg := keybase1.TeamSetSettingsArg{
   289  		TeamID: c.teamID,
   290  		Settings: keybase1.TeamSettings{
   291  			Open:   open,
   292  			JoinAs: joinAsRole,
   293  		},
   294  	}
   295  
   296  	err := cli.TeamSetSettings(ctx, arg)
   297  	if err != nil {
   298  		if e, ok := err.(libkb.NoOpError); ok {
   299  			dui.Printf("%s\n", e.Desc)
   300  			return nil
   301  		}
   302  
   303  		return err
   304  	}
   305  
   306  	if open {
   307  		dui.Printf("Team set to open.\n")
   308  	} else {
   309  		dui.Printf("Team set to closed.\n")
   310  	}
   311  	return nil
   312  }
   313  
   314  func (c *CmdTeamSettings) setProfilePromote(ctx context.Context, cli keybase1.TeamsClient, promote bool) error {
   315  	err := cli.SetTeamMemberShowcase(ctx, keybase1.SetTeamMemberShowcaseArg{
   316  		TeamID:      c.teamID,
   317  		IsShowcased: promote,
   318  	})
   319  	if err != nil {
   320  		if _, ok := err.(libkb.TeamBadMembershipError); ok {
   321  			dui := c.G().UI.GetTerminalUI()
   322  			dui.Printf("You cannot promote team %q on your profile because you\n", c.Team)
   323  			dui.Printf("are not a member of the team.\n\n")
   324  			dui.Printf("You can add yourself to the team with `keybase team add-member ...`\n")
   325  			dui.Printf("and then you can promote the team on your profile.\n\n")
   326  			return nil
   327  		}
   328  		return err
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  func (c *CmdTeamSettings) setAllowProfilePromote(ctx context.Context, cli keybase1.TeamsClient, allow bool) error {
   335  	return cli.SetTeamShowcase(ctx, keybase1.SetTeamShowcaseArg{
   336  		TeamID:            c.teamID,
   337  		IsShowcased:       nil,
   338  		Description:       nil,
   339  		AnyMemberShowcase: &allow,
   340  	})
   341  }
   342  
   343  func (c *CmdTeamSettings) setShowcase(ctx context.Context, cli keybase1.TeamsClient, showcase bool) error {
   344  	return cli.SetTeamShowcase(ctx, keybase1.SetTeamShowcaseArg{
   345  		TeamID:            c.teamID,
   346  		IsShowcased:       &showcase,
   347  		Description:       nil,
   348  		AnyMemberShowcase: nil,
   349  	})
   350  }
   351  
   352  func (c *CmdTeamSettings) setDisableAccessRequests(ctx context.Context, cli keybase1.TeamsClient, disabled bool) error {
   353  	return cli.SetTarsDisabled(ctx, keybase1.SetTarsDisabledArg{
   354  		TeamID:   c.teamID,
   355  		Disabled: disabled,
   356  	})
   357  }
   358  
   359  func (c *CmdTeamSettings) setWelcomeMessage(ctx context.Context, welcomeMessage string) error {
   360  	if err := CheckAndStartStandaloneChat(c.G(), chat1.ConversationMembersType_TEAM); err != nil {
   361  		return err
   362  	}
   363  	msg := chat1.WelcomeMessage{Set: true, Raw: welcomeMessage}
   364  	cli, err := GetChatLocalClient(c.G())
   365  	if err != nil {
   366  		return err
   367  	}
   368  	return cli.SetWelcomeMessage(ctx, chat1.SetWelcomeMessageArg{
   369  		TeamID:  c.teamID,
   370  		Message: msg,
   371  	})
   372  }
   373  
   374  func (c *CmdTeamSettings) resetWelcomeMessage(ctx context.Context) error {
   375  	if err := CheckAndStartStandaloneChat(c.G(), chat1.ConversationMembersType_TEAM); err != nil {
   376  		return err
   377  	}
   378  	cli, err := GetChatLocalClient(c.G())
   379  	if err != nil {
   380  		return err
   381  	}
   382  	return cli.SetWelcomeMessage(ctx, chat1.SetWelcomeMessageArg{
   383  		TeamID:  c.teamID,
   384  		Message: chat1.WelcomeMessage{Set: false},
   385  	})
   386  }
   387  
   388  func (c *CmdTeamSettings) printCurrentSettings(ctx context.Context, cli keybase1.TeamsClient) error {
   389  	res, err := cli.GetAnnotatedTeamByName(ctx, c.Team.String())
   390  	if err != nil {
   391  		return err
   392  	}
   393  
   394  	var showcaseInfo *keybase1.TeamAndMemberShowcase
   395  	tmp, err := cli.GetTeamAndMemberShowcase(ctx, c.teamID)
   396  	if err != nil {
   397  		c.G().Log.CDebugf(ctx, "failed to get team showcase info: %v", err)
   398  	} else {
   399  		showcaseInfo = &tmp
   400  	}
   401  
   402  	dui := c.G().UI.GetTerminalUI()
   403  	dui.Printf("Current settings for team %q:\n", c.Team.String())
   404  	if showcaseInfo != nil && showcaseInfo.TeamShowcase.Description != nil {
   405  		dui.Printf("  Description:             %v\n", *showcaseInfo.TeamShowcase.Description)
   406  	}
   407  	dui.Printf("  Open:                     %v\n", c.tfToYn(res.Settings.Open,
   408  		fmt.Sprintf("default membership = %v", strings.ToLower(res.Settings.JoinAs.String()))))
   409  	if showcaseInfo != nil {
   410  		dui.Printf("  Showcased:                %v\n", c.tfToYn(showcaseInfo.TeamShowcase.IsShowcased, "on keybase.io/popular-teams"))
   411  		dui.Printf("  Promoted:                 %v\n", c.tfToYn(showcaseInfo.IsMemberShowcased, "on your profile"))
   412  		dui.Printf("  Non-admins can promote:   %v\n", c.tfToYn(showcaseInfo.TeamShowcase.AnyMemberShowcase, "on their profiles"))
   413  	}
   414  
   415  	// TarsDisabled info is only available for owners and admins, check
   416  	// if we can make this call so we don't make a GetTarsDisabled call
   417  	// that results in an error which is sent to the console.
   418  	ret, err := cli.CanUserPerform(ctx, c.Team.String())
   419  	if err != nil {
   420  		c.G().Log.CDebugf(ctx, "failed to get CanUserPerform info: %v", err)
   421  	} else {
   422  		if ret.ChangeTarsDisabled {
   423  			ok, err := cli.GetTarsDisabled(ctx, c.teamID)
   424  			if err != nil {
   425  				c.G().Log.CDebugf(ctx, "failed to call GetTarsEnabled: %v", err)
   426  			} else {
   427  				dui.Printf("  Access requests disabled: %v\n", c.tfToYn(ok, ""))
   428  			}
   429  		}
   430  	}
   431  
   432  	err = CheckAndStartStandaloneChat(c.G(), chat1.ConversationMembersType_TEAM)
   433  	if err != nil {
   434  		dui.Printf("  Welcome message: [failed to start chat system, not available in standalone mode]\n")
   435  	} else {
   436  		chatCli, err := GetChatLocalClient(c.G())
   437  		if err != nil {
   438  			return err
   439  		}
   440  		msg, err := chatCli.GetWelcomeMessage(ctx, c.teamID)
   441  		if err != nil {
   442  			c.G().Log.CWarningf(ctx, "failed to call get welcome message: %v", err)
   443  		} else {
   444  			if msg.Set {
   445  				if len(msg.Raw) > 0 {
   446  					dui.Printf("  Welcome message:          %q\n", msg.Raw)
   447  				} else {
   448  					dui.Printf("  Welcome message:          none\n")
   449  				}
   450  			} else {
   451  				dui.Printf("  Welcome message:          unset (default)\n")
   452  
   453  			}
   454  		}
   455  	}
   456  
   457  	return nil
   458  }
   459  
   460  func (c *CmdTeamSettings) tfToYn(x bool, parenthetical string) string {
   461  	if x {
   462  		if len(parenthetical) > 0 {
   463  			return fmt.Sprintf("yes (%v)", parenthetical)
   464  		}
   465  		return "yes"
   466  	}
   467  	return "no"
   468  }
   469  
   470  func (c *CmdTeamSettings) GetUsage() libkb.Usage {
   471  	return libkb.Usage{
   472  		Config:    true,
   473  		API:       true,
   474  		KbKeyring: true,
   475  	}
   476  }