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 }