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.

550 lines
12 KiB

  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. "fmt"
  8. "os"
  9. "text/tabwriter"
  10. "code.gitea.io/git"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/auth/oauth2"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. "github.com/urfave/cli"
  16. )
  17. var (
  18. // CmdAdmin represents the available admin sub-command.
  19. CmdAdmin = cli.Command{
  20. Name: "admin",
  21. Usage: "Command line interface to perform common administrative operations",
  22. Subcommands: []cli.Command{
  23. subcmdCreateUser,
  24. subcmdChangePassword,
  25. subcmdRepoSyncReleases,
  26. subcmdRegenerate,
  27. subcmdAuth,
  28. },
  29. }
  30. subcmdCreateUser = cli.Command{
  31. Name: "create-user",
  32. Usage: "Create a new user in database",
  33. Action: runCreateUser,
  34. Flags: []cli.Flag{
  35. cli.StringFlag{
  36. Name: "name",
  37. Usage: "Username",
  38. },
  39. cli.StringFlag{
  40. Name: "password",
  41. Usage: "User password",
  42. },
  43. cli.StringFlag{
  44. Name: "email",
  45. Usage: "User email address",
  46. },
  47. cli.BoolFlag{
  48. Name: "admin",
  49. Usage: "User is an admin",
  50. },
  51. cli.StringFlag{
  52. Name: "config, c",
  53. Value: "custom/conf/app.ini",
  54. Usage: "Custom configuration file path",
  55. },
  56. },
  57. }
  58. subcmdChangePassword = cli.Command{
  59. Name: "change-password",
  60. Usage: "Change a user's password",
  61. Action: runChangePassword,
  62. Flags: []cli.Flag{
  63. cli.StringFlag{
  64. Name: "username,u",
  65. Value: "",
  66. Usage: "The user to change password for",
  67. },
  68. cli.StringFlag{
  69. Name: "password,p",
  70. Value: "",
  71. Usage: "New password to set for user",
  72. },
  73. cli.StringFlag{
  74. Name: "config, c",
  75. Value: "custom/conf/app.ini",
  76. Usage: "Custom configuration file path",
  77. },
  78. },
  79. }
  80. subcmdRepoSyncReleases = cli.Command{
  81. Name: "repo-sync-releases",
  82. Usage: "Synchronize repository releases with tags",
  83. Action: runRepoSyncReleases,
  84. }
  85. subcmdRegenerate = cli.Command{
  86. Name: "regenerate",
  87. Usage: "Regenerate specific files",
  88. Subcommands: []cli.Command{
  89. microcmdRegenHooks,
  90. microcmdRegenKeys,
  91. },
  92. }
  93. microcmdRegenHooks = cli.Command{
  94. Name: "hooks",
  95. Usage: "Regenerate git-hooks",
  96. Action: runRegenerateHooks,
  97. Flags: []cli.Flag{
  98. cli.StringFlag{
  99. Name: "config, c",
  100. Value: "custom/conf/app.ini",
  101. Usage: "Custom configuration file path",
  102. },
  103. },
  104. }
  105. microcmdRegenKeys = cli.Command{
  106. Name: "keys",
  107. Usage: "Regenerate authorized_keys file",
  108. Action: runRegenerateKeys,
  109. Flags: []cli.Flag{
  110. cli.StringFlag{
  111. Name: "config, c",
  112. Value: "custom/conf/app.ini",
  113. Usage: "Custom configuration file path",
  114. },
  115. },
  116. }
  117. subcmdAuth = cli.Command{
  118. Name: "auth",
  119. Usage: "Modify external auth providers",
  120. Subcommands: []cli.Command{
  121. microcmdAuthAddOauth,
  122. microcmdAuthUpdateOauth,
  123. microcmdAuthList,
  124. microcmdAuthDelete,
  125. },
  126. }
  127. microcmdAuthList = cli.Command{
  128. Name: "list",
  129. Usage: "List auth sources",
  130. Action: runListAuth,
  131. Flags: []cli.Flag{
  132. cli.StringFlag{
  133. Name: "config, c",
  134. Value: "custom/conf/app.ini",
  135. Usage: "Custom configuration file path",
  136. },
  137. },
  138. }
  139. idFlag = cli.Int64Flag{
  140. Name: "id",
  141. Usage: "ID of OAuth authentication source",
  142. }
  143. microcmdAuthDelete = cli.Command{
  144. Name: "delete",
  145. Usage: "Delete specific auth source",
  146. Action: runDeleteAuth,
  147. Flags: []cli.Flag{
  148. cli.StringFlag{
  149. Name: "config, c",
  150. Value: "custom/conf/app.ini",
  151. Usage: "Custom configuration file path",
  152. },
  153. idFlag,
  154. },
  155. }
  156. oauthCLIFlags = []cli.Flag{
  157. cli.StringFlag{
  158. Name: "config, c",
  159. Value: "custom/conf/app.ini",
  160. Usage: "Custom configuration file path",
  161. },
  162. cli.StringFlag{
  163. Name: "name",
  164. Value: "",
  165. Usage: "Application Name",
  166. },
  167. cli.StringFlag{
  168. Name: "provider",
  169. Value: "",
  170. Usage: "OAuth2 Provider",
  171. },
  172. cli.StringFlag{
  173. Name: "key",
  174. Value: "",
  175. Usage: "Client ID (Key)",
  176. },
  177. cli.StringFlag{
  178. Name: "secret",
  179. Value: "",
  180. Usage: "Client Secret",
  181. },
  182. cli.StringFlag{
  183. Name: "auto-discover-url",
  184. Value: "",
  185. Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
  186. },
  187. cli.StringFlag{
  188. Name: "use-custom-urls",
  189. Value: "false",
  190. Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
  191. },
  192. cli.StringFlag{
  193. Name: "custom-auth-url",
  194. Value: "",
  195. Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
  196. },
  197. cli.StringFlag{
  198. Name: "custom-token-url",
  199. Value: "",
  200. Usage: "Use a custom Token URL (option for GitLab/GitHub)",
  201. },
  202. cli.StringFlag{
  203. Name: "custom-profile-url",
  204. Value: "",
  205. Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
  206. },
  207. cli.StringFlag{
  208. Name: "custom-email-url",
  209. Value: "",
  210. Usage: "Use a custom Email URL (option for GitHub)",
  211. },
  212. }
  213. microcmdAuthUpdateOauth = cli.Command{
  214. Name: "update-oauth",
  215. Usage: "Update existing Oauth authentication source",
  216. Action: runUpdateOauth,
  217. Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
  218. }
  219. microcmdAuthAddOauth = cli.Command{
  220. Name: "add-oauth",
  221. Usage: "Add new Oauth authentication source",
  222. Action: runAddOauth,
  223. Flags: oauthCLIFlags,
  224. }
  225. )
  226. func runChangePassword(c *cli.Context) error {
  227. if err := argsSet(c, "username", "password"); err != nil {
  228. return err
  229. }
  230. if c.IsSet("config") {
  231. setting.CustomConf = c.String("config")
  232. }
  233. if err := initDB(); err != nil {
  234. return err
  235. }
  236. uname := c.String("username")
  237. user, err := models.GetUserByName(uname)
  238. if err != nil {
  239. return err
  240. }
  241. if user.Salt, err = models.GetUserSalt(); err != nil {
  242. return err
  243. }
  244. user.HashPassword(c.String("password"))
  245. if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil {
  246. return err
  247. }
  248. fmt.Printf("%s's password has been successfully updated!\n", user.Name)
  249. return nil
  250. }
  251. func runCreateUser(c *cli.Context) error {
  252. if err := argsSet(c, "name", "password", "email"); err != nil {
  253. return err
  254. }
  255. if c.IsSet("config") {
  256. setting.CustomConf = c.String("config")
  257. }
  258. if err := initDB(); err != nil {
  259. return err
  260. }
  261. if err := models.CreateUser(&models.User{
  262. Name: c.String("name"),
  263. Email: c.String("email"),
  264. Passwd: c.String("password"),
  265. IsActive: true,
  266. IsAdmin: c.Bool("admin"),
  267. }); err != nil {
  268. return fmt.Errorf("CreateUser: %v", err)
  269. }
  270. fmt.Printf("New user '%s' has been successfully created!\n", c.String("name"))
  271. return nil
  272. }
  273. func runRepoSyncReleases(c *cli.Context) error {
  274. if err := initDB(); err != nil {
  275. return err
  276. }
  277. log.Trace("Synchronizing repository releases (this may take a while)")
  278. for page := 1; ; page++ {
  279. repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
  280. Page: page,
  281. PageSize: models.RepositoryListDefaultPageSize,
  282. Private: true,
  283. })
  284. if err != nil {
  285. return fmt.Errorf("SearchRepositoryByName: %v", err)
  286. }
  287. if len(repos) == 0 {
  288. break
  289. }
  290. log.Trace("Processing next %d repos of %d", len(repos), count)
  291. for _, repo := range repos {
  292. log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
  293. gitRepo, err := git.OpenRepository(repo.RepoPath())
  294. if err != nil {
  295. log.Warn("OpenRepository: %v", err)
  296. continue
  297. }
  298. oldnum, err := getReleaseCount(repo.ID)
  299. if err != nil {
  300. log.Warn(" GetReleaseCountByRepoID: %v", err)
  301. }
  302. log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
  303. if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
  304. log.Warn(" SyncReleasesWithTags: %v", err)
  305. continue
  306. }
  307. count, err = getReleaseCount(repo.ID)
  308. if err != nil {
  309. log.Warn(" GetReleaseCountByRepoID: %v", err)
  310. continue
  311. }
  312. log.Trace(" repo %s releases synchronized to tags: from %d to %d",
  313. repo.FullName(), oldnum, count)
  314. }
  315. }
  316. return nil
  317. }
  318. func getReleaseCount(id int64) (int64, error) {
  319. return models.GetReleaseCountByRepoID(
  320. id,
  321. models.FindReleasesOptions{
  322. IncludeTags: true,
  323. },
  324. )
  325. }
  326. func runRegenerateHooks(c *cli.Context) error {
  327. if c.IsSet("config") {
  328. setting.CustomConf = c.String("config")
  329. }
  330. if err := initDB(); err != nil {
  331. return err
  332. }
  333. return models.SyncRepositoryHooks()
  334. }
  335. func runRegenerateKeys(c *cli.Context) error {
  336. if c.IsSet("config") {
  337. setting.CustomConf = c.String("config")
  338. }
  339. if err := initDB(); err != nil {
  340. return err
  341. }
  342. return models.RewriteAllPublicKeys()
  343. }
  344. func parseOAuth2Config(c *cli.Context) *models.OAuth2Config {
  345. var customURLMapping *oauth2.CustomURLMapping
  346. if c.IsSet("use-custom-urls") {
  347. customURLMapping = &oauth2.CustomURLMapping{
  348. TokenURL: c.String("custom-token-url"),
  349. AuthURL: c.String("custom-auth-url"),
  350. ProfileURL: c.String("custom-profile-url"),
  351. EmailURL: c.String("custom-email-url"),
  352. }
  353. } else {
  354. customURLMapping = nil
  355. }
  356. return &models.OAuth2Config{
  357. Provider: c.String("provider"),
  358. ClientID: c.String("key"),
  359. ClientSecret: c.String("secret"),
  360. OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
  361. CustomURLMapping: customURLMapping,
  362. }
  363. }
  364. func runAddOauth(c *cli.Context) error {
  365. if c.IsSet("config") {
  366. setting.CustomConf = c.String("config")
  367. }
  368. if err := initDB(); err != nil {
  369. return err
  370. }
  371. if err := models.CreateLoginSource(&models.LoginSource{
  372. Type: models.LoginOAuth2,
  373. Name: c.String("name"),
  374. IsActived: true,
  375. Cfg: parseOAuth2Config(c),
  376. }); err != nil {
  377. return err
  378. }
  379. return nil
  380. }
  381. func runUpdateOauth(c *cli.Context) error {
  382. if c.IsSet("config") {
  383. setting.CustomConf = c.String("config")
  384. }
  385. if !c.IsSet("id") {
  386. return fmt.Errorf("--id flag is missing")
  387. }
  388. if err := initDB(); err != nil {
  389. return err
  390. }
  391. source, err := models.GetLoginSourceByID(c.Int64("id"))
  392. if err != nil {
  393. return err
  394. }
  395. oAuth2Config := source.OAuth2()
  396. if c.IsSet("name") {
  397. source.Name = c.String("name")
  398. }
  399. if c.IsSet("provider") {
  400. oAuth2Config.Provider = c.String("provider")
  401. }
  402. if c.IsSet("key") {
  403. oAuth2Config.ClientID = c.String("key")
  404. }
  405. if c.IsSet("secret") {
  406. oAuth2Config.ClientSecret = c.String("secret")
  407. }
  408. if c.IsSet("auto-discover-url") {
  409. oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
  410. }
  411. // update custom URL mapping
  412. var customURLMapping *oauth2.CustomURLMapping
  413. if oAuth2Config.CustomURLMapping != nil {
  414. customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
  415. customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
  416. customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
  417. customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
  418. }
  419. if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
  420. customURLMapping.TokenURL = c.String("custom-token-url")
  421. }
  422. if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
  423. customURLMapping.AuthURL = c.String("custom-auth-url")
  424. }
  425. if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
  426. customURLMapping.ProfileURL = c.String("custom-profile-url")
  427. }
  428. if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
  429. customURLMapping.EmailURL = c.String("custom-email-url")
  430. }
  431. oAuth2Config.CustomURLMapping = customURLMapping
  432. source.Cfg = oAuth2Config
  433. if err := models.UpdateSource(source); err != nil {
  434. return err
  435. }
  436. return nil
  437. }
  438. func runListAuth(c *cli.Context) error {
  439. if c.IsSet("config") {
  440. setting.CustomConf = c.String("config")
  441. }
  442. if err := initDB(); err != nil {
  443. return err
  444. }
  445. loginSources, err := models.LoginSources()
  446. if err != nil {
  447. return err
  448. }
  449. // loop through each source and print
  450. w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
  451. fmt.Fprintf(w, "ID\tName\tType\tEnabled")
  452. for _, source := range loginSources {
  453. fmt.Fprintf(w, "%d\t%s\t%s\t%t", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived)
  454. }
  455. w.Flush()
  456. return nil
  457. }
  458. func runDeleteAuth(c *cli.Context) error {
  459. if c.IsSet("config") {
  460. setting.CustomConf = c.String("config")
  461. }
  462. if !c.IsSet("id") {
  463. return fmt.Errorf("--id flag is missing")
  464. }
  465. if err := initDB(); err != nil {
  466. return err
  467. }
  468. source, err := models.GetLoginSourceByID(c.Int64("id"))
  469. if err != nil {
  470. return err
  471. }
  472. if err = models.DeleteSource(source); err != nil {
  473. return err
  474. }
  475. return nil
  476. }