You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

admin.go 26KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2016 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package cmd
  6. import (
  7. "context"
  8. "errors"
  9. "fmt"
  10. "os"
  11. "strings"
  12. "text/tabwriter"
  13. "code.gitea.io/gitea/models"
  14. asymkey_model "code.gitea.io/gitea/models/asymkey"
  15. "code.gitea.io/gitea/models/auth"
  16. "code.gitea.io/gitea/models/db"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/graceful"
  20. "code.gitea.io/gitea/modules/log"
  21. pwd "code.gitea.io/gitea/modules/password"
  22. repo_module "code.gitea.io/gitea/modules/repository"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/storage"
  25. "code.gitea.io/gitea/modules/util"
  26. auth_service "code.gitea.io/gitea/services/auth"
  27. "code.gitea.io/gitea/services/auth/source/oauth2"
  28. "code.gitea.io/gitea/services/auth/source/smtp"
  29. repo_service "code.gitea.io/gitea/services/repository"
  30. user_service "code.gitea.io/gitea/services/user"
  31. "github.com/urfave/cli"
  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. subcmdUser = cli.Command{
  47. Name: "user",
  48. Usage: "Modify users",
  49. Subcommands: []cli.Command{
  50. microcmdUserCreate,
  51. microcmdUserList,
  52. microcmdUserChangePassword,
  53. microcmdUserDelete,
  54. microcmdUserGenerateAccessToken,
  55. },
  56. }
  57. microcmdUserList = cli.Command{
  58. Name: "list",
  59. Usage: "List users",
  60. Action: runListUsers,
  61. Flags: []cli.Flag{
  62. cli.BoolFlag{
  63. Name: "admin",
  64. Usage: "List only admin users",
  65. },
  66. },
  67. }
  68. microcmdUserCreate = cli.Command{
  69. Name: "create",
  70. Usage: "Create a new user in database",
  71. Action: runCreateUser,
  72. Flags: []cli.Flag{
  73. cli.StringFlag{
  74. Name: "name",
  75. Usage: "Username. DEPRECATED: use username instead",
  76. },
  77. cli.StringFlag{
  78. Name: "username",
  79. Usage: "Username",
  80. },
  81. cli.StringFlag{
  82. Name: "password",
  83. Usage: "User password",
  84. },
  85. cli.StringFlag{
  86. Name: "email",
  87. Usage: "User email address",
  88. },
  89. cli.BoolFlag{
  90. Name: "admin",
  91. Usage: "User is an admin",
  92. },
  93. cli.BoolFlag{
  94. Name: "random-password",
  95. Usage: "Generate a random password for the user",
  96. },
  97. cli.BoolFlag{
  98. Name: "must-change-password",
  99. Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
  100. },
  101. cli.IntFlag{
  102. Name: "random-password-length",
  103. Usage: "Length of the random password to be generated",
  104. Value: 12,
  105. },
  106. cli.BoolFlag{
  107. Name: "access-token",
  108. Usage: "Generate access token for the user",
  109. },
  110. cli.BoolFlag{
  111. Name: "restricted",
  112. Usage: "Make a restricted user account",
  113. },
  114. },
  115. }
  116. microcmdUserChangePassword = cli.Command{
  117. Name: "change-password",
  118. Usage: "Change a user's password",
  119. Action: runChangePassword,
  120. Flags: []cli.Flag{
  121. cli.StringFlag{
  122. Name: "username,u",
  123. Value: "",
  124. Usage: "The user to change password for",
  125. },
  126. cli.StringFlag{
  127. Name: "password,p",
  128. Value: "",
  129. Usage: "New password to set for user",
  130. },
  131. },
  132. }
  133. microcmdUserDelete = cli.Command{
  134. Name: "delete",
  135. Usage: "Delete specific user by id, name or email",
  136. Flags: []cli.Flag{
  137. cli.Int64Flag{
  138. Name: "id",
  139. Usage: "ID of user of the user to delete",
  140. },
  141. cli.StringFlag{
  142. Name: "username,u",
  143. Usage: "Username of the user to delete",
  144. },
  145. cli.StringFlag{
  146. Name: "email,e",
  147. Usage: "Email of the user to delete",
  148. },
  149. },
  150. Action: runDeleteUser,
  151. }
  152. microcmdUserGenerateAccessToken = cli.Command{
  153. Name: "generate-access-token",
  154. Usage: "Generate a access token for a specific user",
  155. Flags: []cli.Flag{
  156. cli.StringFlag{
  157. Name: "username,u",
  158. Usage: "Username",
  159. },
  160. cli.StringFlag{
  161. Name: "token-name,t",
  162. Usage: "Token name",
  163. Value: "gitea-admin",
  164. },
  165. cli.BoolFlag{
  166. Name: "raw",
  167. Usage: "Display only the token value",
  168. },
  169. },
  170. Action: runGenerateAccessToken,
  171. }
  172. subcmdRepoSyncReleases = cli.Command{
  173. Name: "repo-sync-releases",
  174. Usage: "Synchronize repository releases with tags",
  175. Action: runRepoSyncReleases,
  176. }
  177. subcmdRegenerate = cli.Command{
  178. Name: "regenerate",
  179. Usage: "Regenerate specific files",
  180. Subcommands: []cli.Command{
  181. microcmdRegenHooks,
  182. microcmdRegenKeys,
  183. },
  184. }
  185. microcmdRegenHooks = cli.Command{
  186. Name: "hooks",
  187. Usage: "Regenerate git-hooks",
  188. Action: runRegenerateHooks,
  189. }
  190. microcmdRegenKeys = cli.Command{
  191. Name: "keys",
  192. Usage: "Regenerate authorized_keys file",
  193. Action: runRegenerateKeys,
  194. }
  195. subcmdAuth = cli.Command{
  196. Name: "auth",
  197. Usage: "Modify external auth providers",
  198. Subcommands: []cli.Command{
  199. microcmdAuthAddOauth,
  200. microcmdAuthUpdateOauth,
  201. cmdAuthAddLdapBindDn,
  202. cmdAuthUpdateLdapBindDn,
  203. cmdAuthAddLdapSimpleAuth,
  204. cmdAuthUpdateLdapSimpleAuth,
  205. microcmdAuthAddSMTP,
  206. microcmdAuthUpdateSMTP,
  207. microcmdAuthList,
  208. microcmdAuthDelete,
  209. },
  210. }
  211. microcmdAuthList = cli.Command{
  212. Name: "list",
  213. Usage: "List auth sources",
  214. Action: runListAuth,
  215. Flags: []cli.Flag{
  216. cli.IntFlag{
  217. Name: "min-width",
  218. Usage: "Minimal cell width including any padding for the formatted table",
  219. Value: 0,
  220. },
  221. cli.IntFlag{
  222. Name: "tab-width",
  223. Usage: "width of tab characters in formatted table (equivalent number of spaces)",
  224. Value: 8,
  225. },
  226. cli.IntFlag{
  227. Name: "padding",
  228. Usage: "padding added to a cell before computing its width",
  229. Value: 1,
  230. },
  231. cli.StringFlag{
  232. Name: "pad-char",
  233. 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)`,
  234. Value: "\t",
  235. },
  236. cli.BoolFlag{
  237. Name: "vertical-bars",
  238. Usage: "Set to true to print vertical bars between columns",
  239. },
  240. },
  241. }
  242. idFlag = cli.Int64Flag{
  243. Name: "id",
  244. Usage: "ID of authentication source",
  245. }
  246. microcmdAuthDelete = cli.Command{
  247. Name: "delete",
  248. Usage: "Delete specific auth source",
  249. Flags: []cli.Flag{idFlag},
  250. Action: runDeleteAuth,
  251. }
  252. oauthCLIFlags = []cli.Flag{
  253. cli.StringFlag{
  254. Name: "name",
  255. Value: "",
  256. Usage: "Application Name",
  257. },
  258. cli.StringFlag{
  259. Name: "provider",
  260. Value: "",
  261. Usage: "OAuth2 Provider",
  262. },
  263. cli.StringFlag{
  264. Name: "key",
  265. Value: "",
  266. Usage: "Client ID (Key)",
  267. },
  268. cli.StringFlag{
  269. Name: "secret",
  270. Value: "",
  271. Usage: "Client Secret",
  272. },
  273. cli.StringFlag{
  274. Name: "auto-discover-url",
  275. Value: "",
  276. Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
  277. },
  278. cli.StringFlag{
  279. Name: "use-custom-urls",
  280. Value: "false",
  281. Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
  282. },
  283. cli.StringFlag{
  284. Name: "custom-auth-url",
  285. Value: "",
  286. Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
  287. },
  288. cli.StringFlag{
  289. Name: "custom-token-url",
  290. Value: "",
  291. Usage: "Use a custom Token URL (option for GitLab/GitHub)",
  292. },
  293. cli.StringFlag{
  294. Name: "custom-profile-url",
  295. Value: "",
  296. Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
  297. },
  298. cli.StringFlag{
  299. Name: "custom-email-url",
  300. Value: "",
  301. Usage: "Use a custom Email URL (option for GitHub)",
  302. },
  303. cli.StringFlag{
  304. Name: "icon-url",
  305. Value: "",
  306. Usage: "Custom icon URL for OAuth2 login source",
  307. },
  308. cli.BoolFlag{
  309. Name: "skip-local-2fa",
  310. Usage: "Set to true to skip local 2fa for users authenticated by this source",
  311. },
  312. cli.StringSliceFlag{
  313. Name: "scopes",
  314. Value: nil,
  315. Usage: "Scopes to request when to authenticate against this OAuth2 source",
  316. },
  317. cli.StringFlag{
  318. Name: "required-claim-name",
  319. Value: "",
  320. Usage: "Claim name that has to be set to allow users to login with this source",
  321. },
  322. cli.StringFlag{
  323. Name: "required-claim-value",
  324. Value: "",
  325. Usage: "Claim value that has to be set to allow users to login with this source",
  326. },
  327. cli.StringFlag{
  328. Name: "group-claim-name",
  329. Value: "",
  330. Usage: "Claim name providing group names for this source",
  331. },
  332. cli.StringFlag{
  333. Name: "admin-group",
  334. Value: "",
  335. Usage: "Group Claim value for administrator users",
  336. },
  337. cli.StringFlag{
  338. Name: "restricted-group",
  339. Value: "",
  340. Usage: "Group Claim value for restricted users",
  341. },
  342. }
  343. microcmdAuthUpdateOauth = cli.Command{
  344. Name: "update-oauth",
  345. Usage: "Update existing Oauth authentication source",
  346. Action: runUpdateOauth,
  347. Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
  348. }
  349. microcmdAuthAddOauth = cli.Command{
  350. Name: "add-oauth",
  351. Usage: "Add new Oauth authentication source",
  352. Action: runAddOauth,
  353. Flags: oauthCLIFlags,
  354. }
  355. subcmdSendMail = cli.Command{
  356. Name: "sendmail",
  357. Usage: "Send a message to all users",
  358. Action: runSendMail,
  359. Flags: []cli.Flag{
  360. cli.StringFlag{
  361. Name: "title",
  362. Usage: `a title of a message`,
  363. Value: "",
  364. },
  365. cli.StringFlag{
  366. Name: "content",
  367. Usage: "a content of a message",
  368. Value: "",
  369. },
  370. cli.BoolFlag{
  371. Name: "force,f",
  372. Usage: "A flag to bypass a confirmation step",
  373. },
  374. },
  375. }
  376. smtpCLIFlags = []cli.Flag{
  377. cli.StringFlag{
  378. Name: "name",
  379. Value: "",
  380. Usage: "Application Name",
  381. },
  382. cli.StringFlag{
  383. Name: "auth-type",
  384. Value: "PLAIN",
  385. Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
  386. },
  387. cli.StringFlag{
  388. Name: "host",
  389. Value: "",
  390. Usage: "SMTP Host",
  391. },
  392. cli.IntFlag{
  393. Name: "port",
  394. Usage: "SMTP Port",
  395. },
  396. cli.BoolTFlag{
  397. Name: "force-smtps",
  398. Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
  399. },
  400. cli.BoolTFlag{
  401. Name: "skip-verify",
  402. Usage: "Skip TLS verify.",
  403. },
  404. cli.StringFlag{
  405. Name: "helo-hostname",
  406. Value: "",
  407. Usage: "Hostname sent with HELO. Leave blank to send current hostname",
  408. },
  409. cli.BoolTFlag{
  410. Name: "disable-helo",
  411. Usage: "Disable SMTP helo.",
  412. },
  413. cli.StringFlag{
  414. Name: "allowed-domains",
  415. Value: "",
  416. Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
  417. },
  418. cli.BoolTFlag{
  419. Name: "skip-local-2fa",
  420. Usage: "Skip 2FA to log on.",
  421. },
  422. cli.BoolTFlag{
  423. Name: "active",
  424. Usage: "This Authentication Source is Activated.",
  425. },
  426. }
  427. microcmdAuthAddSMTP = cli.Command{
  428. Name: "add-smtp",
  429. Usage: "Add new SMTP authentication source",
  430. Action: runAddSMTP,
  431. Flags: smtpCLIFlags,
  432. }
  433. microcmdAuthUpdateSMTP = cli.Command{
  434. Name: "update-smtp",
  435. Usage: "Update existing SMTP authentication source",
  436. Action: runUpdateSMTP,
  437. Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
  438. }
  439. )
  440. func runChangePassword(c *cli.Context) error {
  441. if err := argsSet(c, "username", "password"); err != nil {
  442. return err
  443. }
  444. ctx, cancel := installSignals()
  445. defer cancel()
  446. if err := initDB(ctx); err != nil {
  447. return err
  448. }
  449. if len(c.String("password")) < setting.MinPasswordLength {
  450. return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
  451. }
  452. if !pwd.IsComplexEnough(c.String("password")) {
  453. return errors.New("Password does not meet complexity requirements")
  454. }
  455. pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
  456. if err != nil {
  457. return err
  458. }
  459. if pwned {
  460. return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
  461. }
  462. uname := c.String("username")
  463. user, err := user_model.GetUserByName(ctx, uname)
  464. if err != nil {
  465. return err
  466. }
  467. if err = user.SetPassword(c.String("password")); err != nil {
  468. return err
  469. }
  470. if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
  471. return err
  472. }
  473. fmt.Printf("%s's password has been successfully updated!\n", user.Name)
  474. return nil
  475. }
  476. func runCreateUser(c *cli.Context) error {
  477. if err := argsSet(c, "email"); err != nil {
  478. return err
  479. }
  480. if c.IsSet("name") && c.IsSet("username") {
  481. return errors.New("Cannot set both --name and --username flags")
  482. }
  483. if !c.IsSet("name") && !c.IsSet("username") {
  484. return errors.New("One of --name or --username flags must be set")
  485. }
  486. if c.IsSet("password") && c.IsSet("random-password") {
  487. return errors.New("cannot set both -random-password and -password flags")
  488. }
  489. var username string
  490. if c.IsSet("username") {
  491. username = c.String("username")
  492. } else {
  493. username = c.String("name")
  494. fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
  495. }
  496. ctx, cancel := installSignals()
  497. defer cancel()
  498. if err := initDB(ctx); err != nil {
  499. return err
  500. }
  501. var password string
  502. if c.IsSet("password") {
  503. password = c.String("password")
  504. } else if c.IsSet("random-password") {
  505. var err error
  506. password, err = pwd.Generate(c.Int("random-password-length"))
  507. if err != nil {
  508. return err
  509. }
  510. fmt.Printf("generated random password is '%s'\n", password)
  511. } else {
  512. return errors.New("must set either password or random-password flag")
  513. }
  514. // always default to true
  515. changePassword := true
  516. // If this is the first user being created.
  517. // Take it as the admin and don't force a password update.
  518. if n := user_model.CountUsers(nil); n == 0 {
  519. changePassword = false
  520. }
  521. if c.IsSet("must-change-password") {
  522. changePassword = c.Bool("must-change-password")
  523. }
  524. restricted := util.OptionalBoolNone
  525. if c.IsSet("restricted") {
  526. restricted = util.OptionalBoolOf(c.Bool("restricted"))
  527. }
  528. u := &user_model.User{
  529. Name: username,
  530. Email: c.String("email"),
  531. Passwd: password,
  532. IsAdmin: c.Bool("admin"),
  533. MustChangePassword: changePassword,
  534. }
  535. overwriteDefault := &user_model.CreateUserOverwriteOptions{
  536. IsActive: util.OptionalBoolTrue,
  537. IsRestricted: restricted,
  538. }
  539. if err := user_model.CreateUser(u, overwriteDefault); err != nil {
  540. return fmt.Errorf("CreateUser: %v", err)
  541. }
  542. if c.Bool("access-token") {
  543. t := &models.AccessToken{
  544. Name: "gitea-admin",
  545. UID: u.ID,
  546. }
  547. if err := models.NewAccessToken(t); err != nil {
  548. return err
  549. }
  550. fmt.Printf("Access token was successfully created... %s\n", t.Token)
  551. }
  552. fmt.Printf("New user '%s' has been successfully created!\n", username)
  553. return nil
  554. }
  555. func runListUsers(c *cli.Context) error {
  556. ctx, cancel := installSignals()
  557. defer cancel()
  558. if err := initDB(ctx); err != nil {
  559. return err
  560. }
  561. users, err := user_model.GetAllUsers()
  562. if err != nil {
  563. return err
  564. }
  565. w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
  566. if c.IsSet("admin") {
  567. fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
  568. for _, u := range users {
  569. if u.IsAdmin {
  570. fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
  571. }
  572. }
  573. } else {
  574. fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\n")
  575. for _, u := range users {
  576. fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin)
  577. }
  578. }
  579. w.Flush()
  580. return nil
  581. }
  582. func runDeleteUser(c *cli.Context) error {
  583. if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
  584. return fmt.Errorf("You must provide the id, username or email of a user to delete")
  585. }
  586. ctx, cancel := installSignals()
  587. defer cancel()
  588. if err := initDB(ctx); err != nil {
  589. return err
  590. }
  591. if err := storage.Init(); err != nil {
  592. return err
  593. }
  594. var err error
  595. var user *user_model.User
  596. if c.IsSet("email") {
  597. user, err = user_model.GetUserByEmail(c.String("email"))
  598. } else if c.IsSet("username") {
  599. user, err = user_model.GetUserByName(ctx, c.String("username"))
  600. } else {
  601. user, err = user_model.GetUserByID(c.Int64("id"))
  602. }
  603. if err != nil {
  604. return err
  605. }
  606. if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
  607. return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
  608. }
  609. if c.IsSet("id") && user.ID != c.Int64("id") {
  610. return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
  611. }
  612. return user_service.DeleteUser(user)
  613. }
  614. func runGenerateAccessToken(c *cli.Context) error {
  615. if !c.IsSet("username") {
  616. return fmt.Errorf("You must provide the username to generate a token for them")
  617. }
  618. ctx, cancel := installSignals()
  619. defer cancel()
  620. if err := initDB(ctx); err != nil {
  621. return err
  622. }
  623. user, err := user_model.GetUserByName(ctx, c.String("username"))
  624. if err != nil {
  625. return err
  626. }
  627. t := &models.AccessToken{
  628. Name: c.String("token-name"),
  629. UID: user.ID,
  630. }
  631. if err := models.NewAccessToken(t); err != nil {
  632. return err
  633. }
  634. if c.Bool("raw") {
  635. fmt.Printf("%s\n", t.Token)
  636. } else {
  637. fmt.Printf("Access token was successfully created: %s\n", t.Token)
  638. }
  639. return nil
  640. }
  641. func runRepoSyncReleases(_ *cli.Context) error {
  642. ctx, cancel := installSignals()
  643. defer cancel()
  644. if err := initDB(ctx); err != nil {
  645. return err
  646. }
  647. log.Trace("Synchronizing repository releases (this may take a while)")
  648. for page := 1; ; page++ {
  649. repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
  650. ListOptions: db.ListOptions{
  651. PageSize: models.RepositoryListDefaultPageSize,
  652. Page: page,
  653. },
  654. Private: true,
  655. })
  656. if err != nil {
  657. return fmt.Errorf("SearchRepositoryByName: %v", err)
  658. }
  659. if len(repos) == 0 {
  660. break
  661. }
  662. log.Trace("Processing next %d repos of %d", len(repos), count)
  663. for _, repo := range repos {
  664. log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
  665. gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
  666. if err != nil {
  667. log.Warn("OpenRepository: %v", err)
  668. continue
  669. }
  670. oldnum, err := getReleaseCount(repo.ID)
  671. if err != nil {
  672. log.Warn(" GetReleaseCountByRepoID: %v", err)
  673. }
  674. log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
  675. if err = repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil {
  676. log.Warn(" SyncReleasesWithTags: %v", err)
  677. gitRepo.Close()
  678. continue
  679. }
  680. count, err = getReleaseCount(repo.ID)
  681. if err != nil {
  682. log.Warn(" GetReleaseCountByRepoID: %v", err)
  683. gitRepo.Close()
  684. continue
  685. }
  686. log.Trace(" repo %s releases synchronized to tags: from %d to %d",
  687. repo.FullName(), oldnum, count)
  688. gitRepo.Close()
  689. }
  690. }
  691. return nil
  692. }
  693. func getReleaseCount(id int64) (int64, error) {
  694. return models.GetReleaseCountByRepoID(
  695. id,
  696. models.FindReleasesOptions{
  697. IncludeTags: true,
  698. },
  699. )
  700. }
  701. func runRegenerateHooks(_ *cli.Context) error {
  702. ctx, cancel := installSignals()
  703. defer cancel()
  704. if err := initDB(ctx); err != nil {
  705. return err
  706. }
  707. return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
  708. }
  709. func runRegenerateKeys(_ *cli.Context) error {
  710. ctx, cancel := installSignals()
  711. defer cancel()
  712. if err := initDB(ctx); err != nil {
  713. return err
  714. }
  715. return asymkey_model.RewriteAllPublicKeys()
  716. }
  717. func parseOAuth2Config(c *cli.Context) *oauth2.Source {
  718. var customURLMapping *oauth2.CustomURLMapping
  719. if c.IsSet("use-custom-urls") {
  720. customURLMapping = &oauth2.CustomURLMapping{
  721. TokenURL: c.String("custom-token-url"),
  722. AuthURL: c.String("custom-auth-url"),
  723. ProfileURL: c.String("custom-profile-url"),
  724. EmailURL: c.String("custom-email-url"),
  725. }
  726. } else {
  727. customURLMapping = nil
  728. }
  729. return &oauth2.Source{
  730. Provider: c.String("provider"),
  731. ClientID: c.String("key"),
  732. ClientSecret: c.String("secret"),
  733. OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
  734. CustomURLMapping: customURLMapping,
  735. IconURL: c.String("icon-url"),
  736. SkipLocalTwoFA: c.Bool("skip-local-2fa"),
  737. Scopes: c.StringSlice("scopes"),
  738. RequiredClaimName: c.String("required-claim-name"),
  739. RequiredClaimValue: c.String("required-claim-value"),
  740. GroupClaimName: c.String("group-claim-name"),
  741. AdminGroup: c.String("admin-group"),
  742. RestrictedGroup: c.String("restricted-group"),
  743. }
  744. }
  745. func runAddOauth(c *cli.Context) error {
  746. ctx, cancel := installSignals()
  747. defer cancel()
  748. if err := initDB(ctx); err != nil {
  749. return err
  750. }
  751. return auth.CreateSource(&auth.Source{
  752. Type: auth.OAuth2,
  753. Name: c.String("name"),
  754. IsActive: true,
  755. Cfg: parseOAuth2Config(c),
  756. })
  757. }
  758. func runUpdateOauth(c *cli.Context) error {
  759. if !c.IsSet("id") {
  760. return fmt.Errorf("--id flag is missing")
  761. }
  762. ctx, cancel := installSignals()
  763. defer cancel()
  764. if err := initDB(ctx); err != nil {
  765. return err
  766. }
  767. source, err := auth.GetSourceByID(c.Int64("id"))
  768. if err != nil {
  769. return err
  770. }
  771. oAuth2Config := source.Cfg.(*oauth2.Source)
  772. if c.IsSet("name") {
  773. source.Name = c.String("name")
  774. }
  775. if c.IsSet("provider") {
  776. oAuth2Config.Provider = c.String("provider")
  777. }
  778. if c.IsSet("key") {
  779. oAuth2Config.ClientID = c.String("key")
  780. }
  781. if c.IsSet("secret") {
  782. oAuth2Config.ClientSecret = c.String("secret")
  783. }
  784. if c.IsSet("auto-discover-url") {
  785. oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
  786. }
  787. if c.IsSet("icon-url") {
  788. oAuth2Config.IconURL = c.String("icon-url")
  789. }
  790. if c.IsSet("scopes") {
  791. oAuth2Config.Scopes = c.StringSlice("scopes")
  792. }
  793. if c.IsSet("required-claim-name") {
  794. oAuth2Config.RequiredClaimName = c.String("required-claim-name")
  795. }
  796. if c.IsSet("required-claim-value") {
  797. oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
  798. }
  799. if c.IsSet("group-claim-name") {
  800. oAuth2Config.GroupClaimName = c.String("group-claim-name")
  801. }
  802. if c.IsSet("admin-group") {
  803. oAuth2Config.AdminGroup = c.String("admin-group")
  804. }
  805. if c.IsSet("restricted-group") {
  806. oAuth2Config.RestrictedGroup = c.String("restricted-group")
  807. }
  808. // update custom URL mapping
  809. customURLMapping := &oauth2.CustomURLMapping{}
  810. if oAuth2Config.CustomURLMapping != nil {
  811. customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
  812. customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
  813. customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
  814. customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
  815. }
  816. if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
  817. customURLMapping.TokenURL = c.String("custom-token-url")
  818. }
  819. if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
  820. customURLMapping.AuthURL = c.String("custom-auth-url")
  821. }
  822. if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
  823. customURLMapping.ProfileURL = c.String("custom-profile-url")
  824. }
  825. if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
  826. customURLMapping.EmailURL = c.String("custom-email-url")
  827. }
  828. oAuth2Config.CustomURLMapping = customURLMapping
  829. source.Cfg = oAuth2Config
  830. return auth.UpdateSource(source)
  831. }
  832. func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
  833. if c.IsSet("auth-type") {
  834. conf.Auth = c.String("auth-type")
  835. validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
  836. if !contains(validAuthTypes, strings.ToUpper(c.String("auth-type"))) {
  837. return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5")
  838. }
  839. conf.Auth = c.String("auth-type")
  840. }
  841. if c.IsSet("host") {
  842. conf.Host = c.String("host")
  843. }
  844. if c.IsSet("port") {
  845. conf.Port = c.Int("port")
  846. }
  847. if c.IsSet("allowed-domains") {
  848. conf.AllowedDomains = c.String("allowed-domains")
  849. }
  850. if c.IsSet("force-smtps") {
  851. conf.ForceSMTPS = c.BoolT("force-smtps")
  852. }
  853. if c.IsSet("skip-verify") {
  854. conf.SkipVerify = c.BoolT("skip-verify")
  855. }
  856. if c.IsSet("helo-hostname") {
  857. conf.HeloHostname = c.String("helo-hostname")
  858. }
  859. if c.IsSet("disable-helo") {
  860. conf.DisableHelo = c.BoolT("disable-helo")
  861. }
  862. if c.IsSet("skip-local-2fa") {
  863. conf.SkipLocalTwoFA = c.BoolT("skip-local-2fa")
  864. }
  865. return nil
  866. }
  867. func runAddSMTP(c *cli.Context) error {
  868. ctx, cancel := installSignals()
  869. defer cancel()
  870. if err := initDB(ctx); err != nil {
  871. return err
  872. }
  873. if !c.IsSet("name") || len(c.String("name")) == 0 {
  874. return errors.New("name must be set")
  875. }
  876. if !c.IsSet("host") || len(c.String("host")) == 0 {
  877. return errors.New("host must be set")
  878. }
  879. if !c.IsSet("port") {
  880. return errors.New("port must be set")
  881. }
  882. active := true
  883. if c.IsSet("active") {
  884. active = c.BoolT("active")
  885. }
  886. var smtpConfig smtp.Source
  887. if err := parseSMTPConfig(c, &smtpConfig); err != nil {
  888. return err
  889. }
  890. // If not set default to PLAIN
  891. if len(smtpConfig.Auth) == 0 {
  892. smtpConfig.Auth = "PLAIN"
  893. }
  894. return auth.CreateSource(&auth.Source{
  895. Type: auth.SMTP,
  896. Name: c.String("name"),
  897. IsActive: active,
  898. Cfg: &smtpConfig,
  899. })
  900. }
  901. func runUpdateSMTP(c *cli.Context) error {
  902. if !c.IsSet("id") {
  903. return fmt.Errorf("--id flag is missing")
  904. }
  905. ctx, cancel := installSignals()
  906. defer cancel()
  907. if err := initDB(ctx); err != nil {
  908. return err
  909. }
  910. source, err := auth.GetSourceByID(c.Int64("id"))
  911. if err != nil {
  912. return err
  913. }
  914. smtpConfig := source.Cfg.(*smtp.Source)
  915. if err := parseSMTPConfig(c, smtpConfig); err != nil {
  916. return err
  917. }
  918. if c.IsSet("name") {
  919. source.Name = c.String("name")
  920. }
  921. if c.IsSet("active") {
  922. source.IsActive = c.BoolT("active")
  923. }
  924. source.Cfg = smtpConfig
  925. return auth.UpdateSource(source)
  926. }
  927. func runListAuth(c *cli.Context) error {
  928. ctx, cancel := installSignals()
  929. defer cancel()
  930. if err := initDB(ctx); err != nil {
  931. return err
  932. }
  933. authSources, err := auth.Sources()
  934. if err != nil {
  935. return err
  936. }
  937. flags := tabwriter.AlignRight
  938. if c.Bool("vertical-bars") {
  939. flags |= tabwriter.Debug
  940. }
  941. padChar := byte('\t')
  942. if len(c.String("pad-char")) > 0 {
  943. padChar = c.String("pad-char")[0]
  944. }
  945. // loop through each source and print
  946. w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
  947. fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
  948. for _, source := range authSources {
  949. fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
  950. }
  951. w.Flush()
  952. return nil
  953. }
  954. func runDeleteAuth(c *cli.Context) error {
  955. if !c.IsSet("id") {
  956. return fmt.Errorf("--id flag is missing")
  957. }
  958. ctx, cancel := installSignals()
  959. defer cancel()
  960. if err := initDB(ctx); err != nil {
  961. return err
  962. }
  963. source, err := auth.GetSourceByID(c.Int64("id"))
  964. if err != nil {
  965. return err
  966. }
  967. return auth_service.DeleteSource(source)
  968. }