gitea源码

user.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package admin
  5. import (
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. asymkey_model "code.gitea.io/gitea/models/asymkey"
  10. "code.gitea.io/gitea/models/auth"
  11. "code.gitea.io/gitea/models/db"
  12. org_model "code.gitea.io/gitea/models/organization"
  13. packages_model "code.gitea.io/gitea/models/packages"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/auth/password"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/optional"
  19. "code.gitea.io/gitea/modules/setting"
  20. api "code.gitea.io/gitea/modules/structs"
  21. "code.gitea.io/gitea/modules/timeutil"
  22. "code.gitea.io/gitea/modules/web"
  23. "code.gitea.io/gitea/routers/api/v1/user"
  24. "code.gitea.io/gitea/routers/api/v1/utils"
  25. asymkey_service "code.gitea.io/gitea/services/asymkey"
  26. "code.gitea.io/gitea/services/context"
  27. "code.gitea.io/gitea/services/convert"
  28. "code.gitea.io/gitea/services/mailer"
  29. user_service "code.gitea.io/gitea/services/user"
  30. )
  31. func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64) {
  32. if sourceID == 0 {
  33. return
  34. }
  35. source, err := auth.GetSourceByID(ctx, sourceID)
  36. if err != nil {
  37. if auth.IsErrSourceNotExist(err) {
  38. ctx.APIError(http.StatusUnprocessableEntity, err)
  39. } else {
  40. ctx.APIErrorInternal(err)
  41. }
  42. return
  43. }
  44. u.LoginType = source.Type
  45. u.LoginSource = source.ID
  46. }
  47. // CreateUser create a user
  48. func CreateUser(ctx *context.APIContext) {
  49. // swagger:operation POST /admin/users admin adminCreateUser
  50. // ---
  51. // summary: Create a user
  52. // consumes:
  53. // - application/json
  54. // produces:
  55. // - application/json
  56. // parameters:
  57. // - name: body
  58. // in: body
  59. // schema:
  60. // "$ref": "#/definitions/CreateUserOption"
  61. // responses:
  62. // "201":
  63. // "$ref": "#/responses/User"
  64. // "400":
  65. // "$ref": "#/responses/error"
  66. // "403":
  67. // "$ref": "#/responses/forbidden"
  68. // "422":
  69. // "$ref": "#/responses/validationError"
  70. form := web.GetForm(ctx).(*api.CreateUserOption)
  71. u := &user_model.User{
  72. Name: form.Username,
  73. FullName: form.FullName,
  74. Email: form.Email,
  75. Passwd: form.Password,
  76. MustChangePassword: true,
  77. LoginType: auth.Plain,
  78. LoginName: form.LoginName,
  79. }
  80. if form.MustChangePassword != nil {
  81. u.MustChangePassword = *form.MustChangePassword
  82. }
  83. parseAuthSource(ctx, u, form.SourceID)
  84. if ctx.Written() {
  85. return
  86. }
  87. if u.LoginType == auth.Plain {
  88. if len(form.Password) < setting.MinPasswordLength {
  89. err := errors.New("PasswordIsRequired")
  90. ctx.APIError(http.StatusBadRequest, err)
  91. return
  92. }
  93. if !password.IsComplexEnough(form.Password) {
  94. err := errors.New("PasswordComplexity")
  95. ctx.APIError(http.StatusBadRequest, err)
  96. return
  97. }
  98. if err := password.IsPwned(ctx, form.Password); err != nil {
  99. if password.IsErrIsPwnedRequest(err) {
  100. log.Error(err.Error())
  101. }
  102. ctx.APIError(http.StatusBadRequest, errors.New("PasswordPwned"))
  103. return
  104. }
  105. }
  106. overwriteDefault := &user_model.CreateUserOverwriteOptions{
  107. IsActive: optional.Some(true),
  108. IsRestricted: optional.FromPtr(form.Restricted),
  109. }
  110. if form.Visibility != "" {
  111. visibility := api.VisibilityModes[form.Visibility]
  112. overwriteDefault.Visibility = &visibility
  113. }
  114. // Update the user creation timestamp. This can only be done after the user
  115. // record has been inserted into the database; the insert intself will always
  116. // set the creation timestamp to "now".
  117. if form.Created != nil {
  118. u.CreatedUnix = timeutil.TimeStamp(form.Created.Unix())
  119. u.UpdatedUnix = u.CreatedUnix
  120. }
  121. if err := user_model.AdminCreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
  122. if user_model.IsErrUserAlreadyExist(err) ||
  123. user_model.IsErrEmailAlreadyUsed(err) ||
  124. db.IsErrNameReserved(err) ||
  125. db.IsErrNameCharsNotAllowed(err) ||
  126. user_model.IsErrEmailCharIsNotSupported(err) ||
  127. user_model.IsErrEmailInvalid(err) ||
  128. db.IsErrNamePatternNotAllowed(err) {
  129. ctx.APIError(http.StatusUnprocessableEntity, err)
  130. } else {
  131. ctx.APIErrorInternal(err)
  132. }
  133. return
  134. }
  135. if !user_model.IsEmailDomainAllowed(u.Email) {
  136. ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", u.Email))
  137. }
  138. log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
  139. // Send email notification.
  140. if form.SendNotify {
  141. mailer.SendRegisterNotifyMail(u)
  142. }
  143. ctx.JSON(http.StatusCreated, convert.ToUser(ctx, u, ctx.Doer))
  144. }
  145. // EditUser api for modifying a user's information
  146. func EditUser(ctx *context.APIContext) {
  147. // swagger:operation PATCH /admin/users/{username} admin adminEditUser
  148. // ---
  149. // summary: Edit an existing user
  150. // consumes:
  151. // - application/json
  152. // produces:
  153. // - application/json
  154. // parameters:
  155. // - name: username
  156. // in: path
  157. // description: username of the user whose data is to be edited
  158. // type: string
  159. // required: true
  160. // - name: body
  161. // in: body
  162. // schema:
  163. // "$ref": "#/definitions/EditUserOption"
  164. // responses:
  165. // "200":
  166. // "$ref": "#/responses/User"
  167. // "400":
  168. // "$ref": "#/responses/error"
  169. // "403":
  170. // "$ref": "#/responses/forbidden"
  171. // "422":
  172. // "$ref": "#/responses/validationError"
  173. form := web.GetForm(ctx).(*api.EditUserOption)
  174. authOpts := &user_service.UpdateAuthOptions{
  175. LoginSource: optional.FromNonDefault(form.SourceID),
  176. LoginName: optional.Some(form.LoginName),
  177. Password: optional.FromNonDefault(form.Password),
  178. MustChangePassword: optional.FromPtr(form.MustChangePassword),
  179. ProhibitLogin: optional.FromPtr(form.ProhibitLogin),
  180. }
  181. if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil {
  182. switch {
  183. case errors.Is(err, password.ErrMinLength):
  184. ctx.APIError(http.StatusBadRequest, fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
  185. case errors.Is(err, password.ErrComplexity):
  186. ctx.APIError(http.StatusBadRequest, err)
  187. case errors.Is(err, password.ErrIsPwned), password.IsErrIsPwnedRequest(err):
  188. ctx.APIError(http.StatusBadRequest, err)
  189. default:
  190. ctx.APIErrorInternal(err)
  191. }
  192. return
  193. }
  194. if form.Email != nil {
  195. if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
  196. switch {
  197. case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
  198. ctx.APIError(http.StatusBadRequest, err)
  199. case user_model.IsErrEmailAlreadyUsed(err):
  200. ctx.APIError(http.StatusBadRequest, err)
  201. default:
  202. ctx.APIErrorInternal(err)
  203. }
  204. return
  205. }
  206. if !user_model.IsEmailDomainAllowed(*form.Email) {
  207. ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email))
  208. }
  209. }
  210. opts := &user_service.UpdateOptions{
  211. FullName: optional.FromPtr(form.FullName),
  212. Website: optional.FromPtr(form.Website),
  213. Location: optional.FromPtr(form.Location),
  214. Description: optional.FromPtr(form.Description),
  215. IsActive: optional.FromPtr(form.Active),
  216. IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
  217. Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
  218. AllowGitHook: optional.FromPtr(form.AllowGitHook),
  219. AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
  220. MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
  221. AllowCreateOrganization: optional.FromPtr(form.AllowCreateOrganization),
  222. IsRestricted: optional.FromPtr(form.Restricted),
  223. }
  224. if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil {
  225. if user_model.IsErrDeleteLastAdminUser(err) {
  226. ctx.APIError(http.StatusBadRequest, err)
  227. } else {
  228. ctx.APIErrorInternal(err)
  229. }
  230. return
  231. }
  232. log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name)
  233. ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer))
  234. }
  235. // DeleteUser api for deleting a user
  236. func DeleteUser(ctx *context.APIContext) {
  237. // swagger:operation DELETE /admin/users/{username} admin adminDeleteUser
  238. // ---
  239. // summary: Delete a user
  240. // produces:
  241. // - application/json
  242. // parameters:
  243. // - name: username
  244. // in: path
  245. // description: username of the user to delete
  246. // type: string
  247. // required: true
  248. // - name: purge
  249. // in: query
  250. // description: purge the user from the system completely
  251. // type: boolean
  252. // responses:
  253. // "204":
  254. // "$ref": "#/responses/empty"
  255. // "403":
  256. // "$ref": "#/responses/forbidden"
  257. // "404":
  258. // "$ref": "#/responses/notFound"
  259. // "422":
  260. // "$ref": "#/responses/validationError"
  261. if ctx.ContextUser.IsOrganization() {
  262. ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
  263. return
  264. }
  265. // admin should not delete themself
  266. if ctx.ContextUser.ID == ctx.Doer.ID {
  267. ctx.APIError(http.StatusUnprocessableEntity, errors.New("you cannot delete yourself"))
  268. return
  269. }
  270. if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil {
  271. if repo_model.IsErrUserOwnRepos(err) ||
  272. org_model.IsErrUserHasOrgs(err) ||
  273. packages_model.IsErrUserOwnPackages(err) ||
  274. user_model.IsErrDeleteLastAdminUser(err) {
  275. ctx.APIError(http.StatusUnprocessableEntity, err)
  276. } else {
  277. ctx.APIErrorInternal(err)
  278. }
  279. return
  280. }
  281. log.Trace("Account deleted by admin(%s): %s", ctx.Doer.Name, ctx.ContextUser.Name)
  282. ctx.Status(http.StatusNoContent)
  283. }
  284. // CreatePublicKey api for creating a public key to a user
  285. func CreatePublicKey(ctx *context.APIContext) {
  286. // swagger:operation POST /admin/users/{username}/keys admin adminCreatePublicKey
  287. // ---
  288. // summary: Add a public key on behalf of a user
  289. // consumes:
  290. // - application/json
  291. // produces:
  292. // - application/json
  293. // parameters:
  294. // - name: username
  295. // in: path
  296. // description: username of the user who is to receive a public key
  297. // type: string
  298. // required: true
  299. // - name: key
  300. // in: body
  301. // schema:
  302. // "$ref": "#/definitions/CreateKeyOption"
  303. // responses:
  304. // "201":
  305. // "$ref": "#/responses/PublicKey"
  306. // "403":
  307. // "$ref": "#/responses/forbidden"
  308. // "422":
  309. // "$ref": "#/responses/validationError"
  310. form := web.GetForm(ctx).(*api.CreateKeyOption)
  311. user.CreateUserPublicKey(ctx, *form, ctx.ContextUser.ID)
  312. }
  313. // DeleteUserPublicKey api for deleting a user's public key
  314. func DeleteUserPublicKey(ctx *context.APIContext) {
  315. // swagger:operation DELETE /admin/users/{username}/keys/{id} admin adminDeleteUserPublicKey
  316. // ---
  317. // summary: Delete a user's public key
  318. // produces:
  319. // - application/json
  320. // parameters:
  321. // - name: username
  322. // in: path
  323. // description: username of the user whose public key is to be deleted
  324. // type: string
  325. // required: true
  326. // - name: id
  327. // in: path
  328. // description: id of the key to delete
  329. // type: integer
  330. // format: int64
  331. // required: true
  332. // responses:
  333. // "204":
  334. // "$ref": "#/responses/empty"
  335. // "403":
  336. // "$ref": "#/responses/forbidden"
  337. // "404":
  338. // "$ref": "#/responses/notFound"
  339. if err := asymkey_service.DeletePublicKey(ctx, ctx.ContextUser, ctx.PathParamInt64("id")); err != nil {
  340. if asymkey_model.IsErrKeyNotExist(err) {
  341. ctx.APIErrorNotFound()
  342. } else if asymkey_model.IsErrKeyAccessDenied(err) {
  343. ctx.APIError(http.StatusForbidden, "You do not have access to this key")
  344. } else {
  345. ctx.APIErrorInternal(err)
  346. }
  347. return
  348. }
  349. log.Trace("Key deleted by admin(%s): %s", ctx.Doer.Name, ctx.ContextUser.Name)
  350. ctx.Status(http.StatusNoContent)
  351. }
  352. // SearchUsers API for getting information of the users according the filter conditions
  353. func SearchUsers(ctx *context.APIContext) {
  354. // swagger:operation GET /admin/users admin adminSearchUsers
  355. // ---
  356. // summary: Search users according filter conditions
  357. // produces:
  358. // - application/json
  359. // parameters:
  360. // - name: source_id
  361. // in: query
  362. // description: ID of the user's login source to search for
  363. // type: integer
  364. // format: int64
  365. // - name: login_name
  366. // in: query
  367. // description: identifier of the user, provided by the external authenticator
  368. // type: string
  369. // - name: page
  370. // in: query
  371. // description: page number of results to return (1-based)
  372. // type: integer
  373. // - name: limit
  374. // in: query
  375. // description: page size of results
  376. // type: integer
  377. // responses:
  378. // "200":
  379. // "$ref": "#/responses/UserList"
  380. // "403":
  381. // "$ref": "#/responses/forbidden"
  382. listOptions := utils.GetListOptions(ctx)
  383. users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
  384. Actor: ctx.Doer,
  385. Type: user_model.UserTypeIndividual,
  386. LoginName: ctx.FormTrim("login_name"),
  387. SourceID: ctx.FormInt64("source_id"),
  388. OrderBy: db.SearchOrderByAlphabetically,
  389. ListOptions: listOptions,
  390. })
  391. if err != nil {
  392. ctx.APIErrorInternal(err)
  393. return
  394. }
  395. results := make([]*api.User, len(users))
  396. for i := range users {
  397. results[i] = convert.ToUser(ctx, users[i], ctx.Doer)
  398. }
  399. ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
  400. ctx.SetTotalCountHeader(maxResults)
  401. ctx.JSON(http.StatusOK, &results)
  402. }
  403. // RenameUser api for renaming a user
  404. func RenameUser(ctx *context.APIContext) {
  405. // swagger:operation POST /admin/users/{username}/rename admin adminRenameUser
  406. // ---
  407. // summary: Rename a user
  408. // produces:
  409. // - application/json
  410. // parameters:
  411. // - name: username
  412. // in: path
  413. // description: current username of the user
  414. // type: string
  415. // required: true
  416. // - name: body
  417. // in: body
  418. // required: true
  419. // schema:
  420. // "$ref": "#/definitions/RenameUserOption"
  421. // responses:
  422. // "204":
  423. // "$ref": "#/responses/empty"
  424. // "403":
  425. // "$ref": "#/responses/forbidden"
  426. // "422":
  427. // "$ref": "#/responses/validationError"
  428. if ctx.ContextUser.IsOrganization() {
  429. ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
  430. return
  431. }
  432. newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
  433. // Check if username has been changed
  434. if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
  435. if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
  436. ctx.APIError(http.StatusUnprocessableEntity, err)
  437. } else {
  438. ctx.APIErrorInternal(err)
  439. }
  440. return
  441. }
  442. ctx.Status(http.StatusNoContent)
  443. }