code.gitea.io/gitea@v1.21.7/cmd/admin.go (about) 1 // Copyright 2016 The Gogs Authors. All rights reserved. 2 // Copyright 2016 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package cmd 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "net/url" 12 "os" 13 "strings" 14 "text/tabwriter" 15 16 asymkey_model "code.gitea.io/gitea/models/asymkey" 17 auth_model "code.gitea.io/gitea/models/auth" 18 "code.gitea.io/gitea/models/db" 19 repo_model "code.gitea.io/gitea/models/repo" 20 "code.gitea.io/gitea/modules/git" 21 "code.gitea.io/gitea/modules/graceful" 22 "code.gitea.io/gitea/modules/log" 23 repo_module "code.gitea.io/gitea/modules/repository" 24 "code.gitea.io/gitea/modules/util" 25 auth_service "code.gitea.io/gitea/services/auth" 26 "code.gitea.io/gitea/services/auth/source/oauth2" 27 "code.gitea.io/gitea/services/auth/source/smtp" 28 repo_service "code.gitea.io/gitea/services/repository" 29 30 "github.com/urfave/cli/v2" 31 ) 32 33 var ( 34 // CmdAdmin represents the available admin sub-command. 35 CmdAdmin = &cli.Command{ 36 Name: "admin", 37 Usage: "Command line interface to perform common administrative operations", 38 Subcommands: []*cli.Command{ 39 subcmdUser, 40 subcmdRepoSyncReleases, 41 subcmdRegenerate, 42 subcmdAuth, 43 subcmdSendMail, 44 }, 45 } 46 47 subcmdRepoSyncReleases = &cli.Command{ 48 Name: "repo-sync-releases", 49 Usage: "Synchronize repository releases with tags", 50 Action: runRepoSyncReleases, 51 } 52 53 subcmdRegenerate = &cli.Command{ 54 Name: "regenerate", 55 Usage: "Regenerate specific files", 56 Subcommands: []*cli.Command{ 57 microcmdRegenHooks, 58 microcmdRegenKeys, 59 }, 60 } 61 62 microcmdRegenHooks = &cli.Command{ 63 Name: "hooks", 64 Usage: "Regenerate git-hooks", 65 Action: runRegenerateHooks, 66 } 67 68 microcmdRegenKeys = &cli.Command{ 69 Name: "keys", 70 Usage: "Regenerate authorized_keys file", 71 Action: runRegenerateKeys, 72 } 73 74 subcmdAuth = &cli.Command{ 75 Name: "auth", 76 Usage: "Modify external auth providers", 77 Subcommands: []*cli.Command{ 78 microcmdAuthAddOauth, 79 microcmdAuthUpdateOauth, 80 cmdAuthAddLdapBindDn, 81 cmdAuthUpdateLdapBindDn, 82 cmdAuthAddLdapSimpleAuth, 83 cmdAuthUpdateLdapSimpleAuth, 84 microcmdAuthAddSMTP, 85 microcmdAuthUpdateSMTP, 86 microcmdAuthList, 87 microcmdAuthDelete, 88 }, 89 } 90 91 microcmdAuthList = &cli.Command{ 92 Name: "list", 93 Usage: "List auth sources", 94 Action: runListAuth, 95 Flags: []cli.Flag{ 96 &cli.IntFlag{ 97 Name: "min-width", 98 Usage: "Minimal cell width including any padding for the formatted table", 99 Value: 0, 100 }, 101 &cli.IntFlag{ 102 Name: "tab-width", 103 Usage: "width of tab characters in formatted table (equivalent number of spaces)", 104 Value: 8, 105 }, 106 &cli.IntFlag{ 107 Name: "padding", 108 Usage: "padding added to a cell before computing its width", 109 Value: 1, 110 }, 111 &cli.StringFlag{ 112 Name: "pad-char", 113 Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`, 114 Value: "\t", 115 }, 116 &cli.BoolFlag{ 117 Name: "vertical-bars", 118 Usage: "Set to true to print vertical bars between columns", 119 }, 120 }, 121 } 122 123 idFlag = &cli.Int64Flag{ 124 Name: "id", 125 Usage: "ID of authentication source", 126 } 127 128 microcmdAuthDelete = &cli.Command{ 129 Name: "delete", 130 Usage: "Delete specific auth source", 131 Flags: []cli.Flag{idFlag}, 132 Action: runDeleteAuth, 133 } 134 135 oauthCLIFlags = []cli.Flag{ 136 &cli.StringFlag{ 137 Name: "name", 138 Value: "", 139 Usage: "Application Name", 140 }, 141 &cli.StringFlag{ 142 Name: "provider", 143 Value: "", 144 Usage: "OAuth2 Provider", 145 }, 146 &cli.StringFlag{ 147 Name: "key", 148 Value: "", 149 Usage: "Client ID (Key)", 150 }, 151 &cli.StringFlag{ 152 Name: "secret", 153 Value: "", 154 Usage: "Client Secret", 155 }, 156 &cli.StringFlag{ 157 Name: "auto-discover-url", 158 Value: "", 159 Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)", 160 }, 161 &cli.StringFlag{ 162 Name: "use-custom-urls", 163 Value: "false", 164 Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints", 165 }, 166 &cli.StringFlag{ 167 Name: "custom-tenant-id", 168 Value: "", 169 Usage: "Use custom Tenant ID for OAuth endpoints", 170 }, 171 &cli.StringFlag{ 172 Name: "custom-auth-url", 173 Value: "", 174 Usage: "Use a custom Authorization URL (option for GitLab/GitHub)", 175 }, 176 &cli.StringFlag{ 177 Name: "custom-token-url", 178 Value: "", 179 Usage: "Use a custom Token URL (option for GitLab/GitHub)", 180 }, 181 &cli.StringFlag{ 182 Name: "custom-profile-url", 183 Value: "", 184 Usage: "Use a custom Profile URL (option for GitLab/GitHub)", 185 }, 186 &cli.StringFlag{ 187 Name: "custom-email-url", 188 Value: "", 189 Usage: "Use a custom Email URL (option for GitHub)", 190 }, 191 &cli.StringFlag{ 192 Name: "icon-url", 193 Value: "", 194 Usage: "Custom icon URL for OAuth2 login source", 195 }, 196 &cli.BoolFlag{ 197 Name: "skip-local-2fa", 198 Usage: "Set to true to skip local 2fa for users authenticated by this source", 199 }, 200 &cli.StringSliceFlag{ 201 Name: "scopes", 202 Value: nil, 203 Usage: "Scopes to request when to authenticate against this OAuth2 source", 204 }, 205 &cli.StringFlag{ 206 Name: "required-claim-name", 207 Value: "", 208 Usage: "Claim name that has to be set to allow users to login with this source", 209 }, 210 &cli.StringFlag{ 211 Name: "required-claim-value", 212 Value: "", 213 Usage: "Claim value that has to be set to allow users to login with this source", 214 }, 215 &cli.StringFlag{ 216 Name: "group-claim-name", 217 Value: "", 218 Usage: "Claim name providing group names for this source", 219 }, 220 &cli.StringFlag{ 221 Name: "admin-group", 222 Value: "", 223 Usage: "Group Claim value for administrator users", 224 }, 225 &cli.StringFlag{ 226 Name: "restricted-group", 227 Value: "", 228 Usage: "Group Claim value for restricted users", 229 }, 230 &cli.StringFlag{ 231 Name: "group-team-map", 232 Value: "", 233 Usage: "JSON mapping between groups and org teams", 234 }, 235 &cli.BoolFlag{ 236 Name: "group-team-map-removal", 237 Usage: "Activate automatic team membership removal depending on groups", 238 }, 239 } 240 241 microcmdAuthUpdateOauth = &cli.Command{ 242 Name: "update-oauth", 243 Usage: "Update existing Oauth authentication source", 244 Action: runUpdateOauth, 245 Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...), 246 } 247 248 microcmdAuthAddOauth = &cli.Command{ 249 Name: "add-oauth", 250 Usage: "Add new Oauth authentication source", 251 Action: runAddOauth, 252 Flags: oauthCLIFlags, 253 } 254 255 subcmdSendMail = &cli.Command{ 256 Name: "sendmail", 257 Usage: "Send a message to all users", 258 Action: runSendMail, 259 Flags: []cli.Flag{ 260 &cli.StringFlag{ 261 Name: "title", 262 Usage: `a title of a message`, 263 Value: "", 264 }, 265 &cli.StringFlag{ 266 Name: "content", 267 Usage: "a content of a message", 268 Value: "", 269 }, 270 &cli.BoolFlag{ 271 Name: "force", 272 Aliases: []string{"f"}, 273 Usage: "A flag to bypass a confirmation step", 274 }, 275 }, 276 } 277 278 smtpCLIFlags = []cli.Flag{ 279 &cli.StringFlag{ 280 Name: "name", 281 Value: "", 282 Usage: "Application Name", 283 }, 284 &cli.StringFlag{ 285 Name: "auth-type", 286 Value: "PLAIN", 287 Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN", 288 }, 289 &cli.StringFlag{ 290 Name: "host", 291 Value: "", 292 Usage: "SMTP Host", 293 }, 294 &cli.IntFlag{ 295 Name: "port", 296 Usage: "SMTP Port", 297 }, 298 &cli.BoolFlag{ 299 Name: "force-smtps", 300 Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", 301 Value: true, 302 }, 303 &cli.BoolFlag{ 304 Name: "skip-verify", 305 Usage: "Skip TLS verify.", 306 Value: true, 307 }, 308 &cli.StringFlag{ 309 Name: "helo-hostname", 310 Value: "", 311 Usage: "Hostname sent with HELO. Leave blank to send current hostname", 312 }, 313 &cli.BoolFlag{ 314 Name: "disable-helo", 315 Usage: "Disable SMTP helo.", 316 Value: true, 317 }, 318 &cli.StringFlag{ 319 Name: "allowed-domains", 320 Value: "", 321 Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')", 322 }, 323 &cli.BoolFlag{ 324 Name: "skip-local-2fa", 325 Usage: "Skip 2FA to log on.", 326 Value: true, 327 }, 328 &cli.BoolFlag{ 329 Name: "active", 330 Usage: "This Authentication Source is Activated.", 331 Value: true, 332 }, 333 } 334 335 microcmdAuthAddSMTP = &cli.Command{ 336 Name: "add-smtp", 337 Usage: "Add new SMTP authentication source", 338 Action: runAddSMTP, 339 Flags: smtpCLIFlags, 340 } 341 342 microcmdAuthUpdateSMTP = &cli.Command{ 343 Name: "update-smtp", 344 Usage: "Update existing SMTP authentication source", 345 Action: runUpdateSMTP, 346 Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...), 347 } 348 ) 349 350 func runRepoSyncReleases(_ *cli.Context) error { 351 ctx, cancel := installSignals() 352 defer cancel() 353 354 if err := initDB(ctx); err != nil { 355 return err 356 } 357 358 if err := git.InitSimple(ctx); err != nil { 359 return err 360 } 361 362 log.Trace("Synchronizing repository releases (this may take a while)") 363 for page := 1; ; page++ { 364 repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{ 365 ListOptions: db.ListOptions{ 366 PageSize: repo_model.RepositoryListDefaultPageSize, 367 Page: page, 368 }, 369 Private: true, 370 }) 371 if err != nil { 372 return fmt.Errorf("SearchRepositoryByName: %w", err) 373 } 374 if len(repos) == 0 { 375 break 376 } 377 log.Trace("Processing next %d repos of %d", len(repos), count) 378 for _, repo := range repos { 379 log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath()) 380 gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) 381 if err != nil { 382 log.Warn("OpenRepository: %v", err) 383 continue 384 } 385 386 oldnum, err := getReleaseCount(ctx, repo.ID) 387 if err != nil { 388 log.Warn(" GetReleaseCountByRepoID: %v", err) 389 } 390 log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) 391 392 if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { 393 log.Warn(" SyncReleasesWithTags: %v", err) 394 gitRepo.Close() 395 continue 396 } 397 398 count, err = getReleaseCount(ctx, repo.ID) 399 if err != nil { 400 log.Warn(" GetReleaseCountByRepoID: %v", err) 401 gitRepo.Close() 402 continue 403 } 404 405 log.Trace(" repo %s releases synchronized to tags: from %d to %d", 406 repo.FullName(), oldnum, count) 407 gitRepo.Close() 408 } 409 } 410 411 return nil 412 } 413 414 func getReleaseCount(ctx context.Context, id int64) (int64, error) { 415 return repo_model.GetReleaseCountByRepoID( 416 ctx, 417 id, 418 repo_model.FindReleasesOptions{ 419 IncludeTags: true, 420 }, 421 ) 422 } 423 424 func runRegenerateHooks(_ *cli.Context) error { 425 ctx, cancel := installSignals() 426 defer cancel() 427 428 if err := initDB(ctx); err != nil { 429 return err 430 } 431 return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) 432 } 433 434 func runRegenerateKeys(_ *cli.Context) error { 435 ctx, cancel := installSignals() 436 defer cancel() 437 438 if err := initDB(ctx); err != nil { 439 return err 440 } 441 return asymkey_model.RewriteAllPublicKeys(ctx) 442 } 443 444 func parseOAuth2Config(c *cli.Context) *oauth2.Source { 445 var customURLMapping *oauth2.CustomURLMapping 446 if c.IsSet("use-custom-urls") { 447 customURLMapping = &oauth2.CustomURLMapping{ 448 TokenURL: c.String("custom-token-url"), 449 AuthURL: c.String("custom-auth-url"), 450 ProfileURL: c.String("custom-profile-url"), 451 EmailURL: c.String("custom-email-url"), 452 Tenant: c.String("custom-tenant-id"), 453 } 454 } else { 455 customURLMapping = nil 456 } 457 return &oauth2.Source{ 458 Provider: c.String("provider"), 459 ClientID: c.String("key"), 460 ClientSecret: c.String("secret"), 461 OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"), 462 CustomURLMapping: customURLMapping, 463 IconURL: c.String("icon-url"), 464 SkipLocalTwoFA: c.Bool("skip-local-2fa"), 465 Scopes: c.StringSlice("scopes"), 466 RequiredClaimName: c.String("required-claim-name"), 467 RequiredClaimValue: c.String("required-claim-value"), 468 GroupClaimName: c.String("group-claim-name"), 469 AdminGroup: c.String("admin-group"), 470 RestrictedGroup: c.String("restricted-group"), 471 GroupTeamMap: c.String("group-team-map"), 472 GroupTeamMapRemoval: c.Bool("group-team-map-removal"), 473 } 474 } 475 476 func runAddOauth(c *cli.Context) error { 477 ctx, cancel := installSignals() 478 defer cancel() 479 480 if err := initDB(ctx); err != nil { 481 return err 482 } 483 484 config := parseOAuth2Config(c) 485 if config.Provider == "openidConnect" { 486 discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL) 487 if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { 488 return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL) 489 } 490 } 491 492 return auth_model.CreateSource(&auth_model.Source{ 493 Type: auth_model.OAuth2, 494 Name: c.String("name"), 495 IsActive: true, 496 Cfg: config, 497 }) 498 } 499 500 func runUpdateOauth(c *cli.Context) error { 501 if !c.IsSet("id") { 502 return fmt.Errorf("--id flag is missing") 503 } 504 505 ctx, cancel := installSignals() 506 defer cancel() 507 508 if err := initDB(ctx); err != nil { 509 return err 510 } 511 512 source, err := auth_model.GetSourceByID(c.Int64("id")) 513 if err != nil { 514 return err 515 } 516 517 oAuth2Config := source.Cfg.(*oauth2.Source) 518 519 if c.IsSet("name") { 520 source.Name = c.String("name") 521 } 522 523 if c.IsSet("provider") { 524 oAuth2Config.Provider = c.String("provider") 525 } 526 527 if c.IsSet("key") { 528 oAuth2Config.ClientID = c.String("key") 529 } 530 531 if c.IsSet("secret") { 532 oAuth2Config.ClientSecret = c.String("secret") 533 } 534 535 if c.IsSet("auto-discover-url") { 536 oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url") 537 } 538 539 if c.IsSet("icon-url") { 540 oAuth2Config.IconURL = c.String("icon-url") 541 } 542 543 if c.IsSet("scopes") { 544 oAuth2Config.Scopes = c.StringSlice("scopes") 545 } 546 547 if c.IsSet("required-claim-name") { 548 oAuth2Config.RequiredClaimName = c.String("required-claim-name") 549 } 550 if c.IsSet("required-claim-value") { 551 oAuth2Config.RequiredClaimValue = c.String("required-claim-value") 552 } 553 554 if c.IsSet("group-claim-name") { 555 oAuth2Config.GroupClaimName = c.String("group-claim-name") 556 } 557 if c.IsSet("admin-group") { 558 oAuth2Config.AdminGroup = c.String("admin-group") 559 } 560 if c.IsSet("restricted-group") { 561 oAuth2Config.RestrictedGroup = c.String("restricted-group") 562 } 563 if c.IsSet("group-team-map") { 564 oAuth2Config.GroupTeamMap = c.String("group-team-map") 565 } 566 if c.IsSet("group-team-map-removal") { 567 oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal") 568 } 569 570 // update custom URL mapping 571 customURLMapping := &oauth2.CustomURLMapping{} 572 573 if oAuth2Config.CustomURLMapping != nil { 574 customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL 575 customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL 576 customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL 577 customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL 578 customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant 579 } 580 if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") { 581 customURLMapping.TokenURL = c.String("custom-token-url") 582 } 583 584 if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") { 585 customURLMapping.AuthURL = c.String("custom-auth-url") 586 } 587 588 if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") { 589 customURLMapping.ProfileURL = c.String("custom-profile-url") 590 } 591 592 if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") { 593 customURLMapping.EmailURL = c.String("custom-email-url") 594 } 595 596 if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") { 597 customURLMapping.Tenant = c.String("custom-tenant-id") 598 } 599 600 oAuth2Config.CustomURLMapping = customURLMapping 601 source.Cfg = oAuth2Config 602 603 return auth_model.UpdateSource(source) 604 } 605 606 func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { 607 if c.IsSet("auth-type") { 608 conf.Auth = c.String("auth-type") 609 validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} 610 if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) { 611 return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5") 612 } 613 conf.Auth = c.String("auth-type") 614 } 615 if c.IsSet("host") { 616 conf.Host = c.String("host") 617 } 618 if c.IsSet("port") { 619 conf.Port = c.Int("port") 620 } 621 if c.IsSet("allowed-domains") { 622 conf.AllowedDomains = c.String("allowed-domains") 623 } 624 if c.IsSet("force-smtps") { 625 conf.ForceSMTPS = c.Bool("force-smtps") 626 } 627 if c.IsSet("skip-verify") { 628 conf.SkipVerify = c.Bool("skip-verify") 629 } 630 if c.IsSet("helo-hostname") { 631 conf.HeloHostname = c.String("helo-hostname") 632 } 633 if c.IsSet("disable-helo") { 634 conf.DisableHelo = c.Bool("disable-helo") 635 } 636 if c.IsSet("skip-local-2fa") { 637 conf.SkipLocalTwoFA = c.Bool("skip-local-2fa") 638 } 639 return nil 640 } 641 642 func runAddSMTP(c *cli.Context) error { 643 ctx, cancel := installSignals() 644 defer cancel() 645 646 if err := initDB(ctx); err != nil { 647 return err 648 } 649 650 if !c.IsSet("name") || len(c.String("name")) == 0 { 651 return errors.New("name must be set") 652 } 653 if !c.IsSet("host") || len(c.String("host")) == 0 { 654 return errors.New("host must be set") 655 } 656 if !c.IsSet("port") { 657 return errors.New("port must be set") 658 } 659 active := true 660 if c.IsSet("active") { 661 active = c.Bool("active") 662 } 663 664 var smtpConfig smtp.Source 665 if err := parseSMTPConfig(c, &smtpConfig); err != nil { 666 return err 667 } 668 669 // If not set default to PLAIN 670 if len(smtpConfig.Auth) == 0 { 671 smtpConfig.Auth = "PLAIN" 672 } 673 674 return auth_model.CreateSource(&auth_model.Source{ 675 Type: auth_model.SMTP, 676 Name: c.String("name"), 677 IsActive: active, 678 Cfg: &smtpConfig, 679 }) 680 } 681 682 func runUpdateSMTP(c *cli.Context) error { 683 if !c.IsSet("id") { 684 return fmt.Errorf("--id flag is missing") 685 } 686 687 ctx, cancel := installSignals() 688 defer cancel() 689 690 if err := initDB(ctx); err != nil { 691 return err 692 } 693 694 source, err := auth_model.GetSourceByID(c.Int64("id")) 695 if err != nil { 696 return err 697 } 698 699 smtpConfig := source.Cfg.(*smtp.Source) 700 701 if err := parseSMTPConfig(c, smtpConfig); err != nil { 702 return err 703 } 704 705 if c.IsSet("name") { 706 source.Name = c.String("name") 707 } 708 709 if c.IsSet("active") { 710 source.IsActive = c.Bool("active") 711 } 712 713 source.Cfg = smtpConfig 714 715 return auth_model.UpdateSource(source) 716 } 717 718 func runListAuth(c *cli.Context) error { 719 ctx, cancel := installSignals() 720 defer cancel() 721 722 if err := initDB(ctx); err != nil { 723 return err 724 } 725 726 authSources, err := auth_model.Sources() 727 if err != nil { 728 return err 729 } 730 731 flags := tabwriter.AlignRight 732 if c.Bool("vertical-bars") { 733 flags |= tabwriter.Debug 734 } 735 736 padChar := byte('\t') 737 if len(c.String("pad-char")) > 0 { 738 padChar = c.String("pad-char")[0] 739 } 740 741 // loop through each source and print 742 w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags) 743 fmt.Fprintf(w, "ID\tName\tType\tEnabled\n") 744 for _, source := range authSources { 745 fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive) 746 } 747 w.Flush() 748 749 return nil 750 } 751 752 func runDeleteAuth(c *cli.Context) error { 753 if !c.IsSet("id") { 754 return fmt.Errorf("--id flag is missing") 755 } 756 757 ctx, cancel := installSignals() 758 defer cancel() 759 760 if err := initDB(ctx); err != nil { 761 return err 762 } 763 764 source, err := auth_model.GetSourceByID(c.Int64("id")) 765 if err != nil { 766 return err 767 } 768 769 return auth_service.DeleteSource(source) 770 }