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.

user.go 37KB

Rewrite logger system (#24726) ## ⚠️ Breaking The `log.<mode>.<logger>` style config has been dropped. If you used it, please check the new config manual & app.example.ini to make your instance output logs as expected. Although many legacy options still work, it's encouraged to upgrade to the new options. The SMTP logger is deleted because SMTP is not suitable to collect logs. If you have manually configured Gitea log options, please confirm the logger system works as expected after upgrading. ## Description Close #12082 and maybe more log-related issues, resolve some related FIXMEs in old code (which seems unfixable before) Just like rewriting queue #24505 : make code maintainable, clear legacy bugs, and add the ability to support more writers (eg: JSON, structured log) There is a new document (with examples): `logging-config.en-us.md` This PR is safer than the queue rewriting, because it's just for logging, it won't break other logic. ## The old problems The logging system is quite old and difficult to maintain: * Unclear concepts: Logger, NamedLogger, MultiChannelledLogger, SubLogger, EventLogger, WriterLogger etc * Some code is diffuclt to konw whether it is right: `log.DelNamedLogger("console")` vs `log.DelNamedLogger(log.DEFAULT)` vs `log.DelLogger("console")` * The old system heavily depends on ini config system, it's difficult to create new logger for different purpose, and it's very fragile. * The "color" trick is difficult to use and read, many colors are unnecessary, and in the future structured log could help * It's difficult to add other log formats, eg: JSON format * The log outputer doesn't have full control of its goroutine, it's difficult to make outputer have advanced behaviors * The logs could be lost in some cases: eg: no Fatal error when using CLI. * Config options are passed by JSON, which is quite fragile. * INI package makes the KEY in `[log]` section visible in `[log.sub1]` and `[log.sub1.subA]`, this behavior is quite fragile and would cause more unclear problems, and there is no strong requirement to support `log.<mode>.<logger>` syntax. ## The new design See `logger.go` for documents. ## Screenshot <details> ![image](https://github.com/go-gitea/gitea/assets/2114189/4462d713-ba39-41f5-bb08-de912e67e1ff) ![image](https://github.com/go-gitea/gitea/assets/2114189/b188035e-f691-428b-8b2d-ff7b2199b2f9) ![image](https://github.com/go-gitea/gitea/assets/2114189/132e9745-1c3b-4e00-9e0d-15eaea495dee) </details> ## TODO * [x] add some new tests * [x] fix some tests * [x] test some sub-commands (manually ....) --------- Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: Giteabot <teabot@gitea.io>
1 year ago
Rewrite logger system (#24726) ## ⚠️ Breaking The `log.<mode>.<logger>` style config has been dropped. If you used it, please check the new config manual & app.example.ini to make your instance output logs as expected. Although many legacy options still work, it's encouraged to upgrade to the new options. The SMTP logger is deleted because SMTP is not suitable to collect logs. If you have manually configured Gitea log options, please confirm the logger system works as expected after upgrading. ## Description Close #12082 and maybe more log-related issues, resolve some related FIXMEs in old code (which seems unfixable before) Just like rewriting queue #24505 : make code maintainable, clear legacy bugs, and add the ability to support more writers (eg: JSON, structured log) There is a new document (with examples): `logging-config.en-us.md` This PR is safer than the queue rewriting, because it's just for logging, it won't break other logic. ## The old problems The logging system is quite old and difficult to maintain: * Unclear concepts: Logger, NamedLogger, MultiChannelledLogger, SubLogger, EventLogger, WriterLogger etc * Some code is diffuclt to konw whether it is right: `log.DelNamedLogger("console")` vs `log.DelNamedLogger(log.DEFAULT)` vs `log.DelLogger("console")` * The old system heavily depends on ini config system, it's difficult to create new logger for different purpose, and it's very fragile. * The "color" trick is difficult to use and read, many colors are unnecessary, and in the future structured log could help * It's difficult to add other log formats, eg: JSON format * The log outputer doesn't have full control of its goroutine, it's difficult to make outputer have advanced behaviors * The logs could be lost in some cases: eg: no Fatal error when using CLI. * Config options are passed by JSON, which is quite fragile. * INI package makes the KEY in `[log]` section visible in `[log.sub1]` and `[log.sub1.subA]`, this behavior is quite fragile and would cause more unclear problems, and there is no strong requirement to support `log.<mode>.<logger>` syntax. ## The new design See `logger.go` for documents. ## Screenshot <details> ![image](https://github.com/go-gitea/gitea/assets/2114189/4462d713-ba39-41f5-bb08-de912e67e1ff) ![image](https://github.com/go-gitea/gitea/assets/2114189/b188035e-f691-428b-8b2d-ff7b2199b2f9) ![image](https://github.com/go-gitea/gitea/assets/2114189/132e9745-1c3b-4e00-9e0d-15eaea495dee) </details> ## TODO * [x] add some new tests * [x] fix some tests * [x] test some sub-commands (manually ....) --------- Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: Giteabot <teabot@gitea.io>
1 year ago
Rewrite logger system (#24726) ## ⚠️ Breaking The `log.<mode>.<logger>` style config has been dropped. If you used it, please check the new config manual & app.example.ini to make your instance output logs as expected. Although many legacy options still work, it's encouraged to upgrade to the new options. The SMTP logger is deleted because SMTP is not suitable to collect logs. If you have manually configured Gitea log options, please confirm the logger system works as expected after upgrading. ## Description Close #12082 and maybe more log-related issues, resolve some related FIXMEs in old code (which seems unfixable before) Just like rewriting queue #24505 : make code maintainable, clear legacy bugs, and add the ability to support more writers (eg: JSON, structured log) There is a new document (with examples): `logging-config.en-us.md` This PR is safer than the queue rewriting, because it's just for logging, it won't break other logic. ## The old problems The logging system is quite old and difficult to maintain: * Unclear concepts: Logger, NamedLogger, MultiChannelledLogger, SubLogger, EventLogger, WriterLogger etc * Some code is diffuclt to konw whether it is right: `log.DelNamedLogger("console")` vs `log.DelNamedLogger(log.DEFAULT)` vs `log.DelLogger("console")` * The old system heavily depends on ini config system, it's difficult to create new logger for different purpose, and it's very fragile. * The "color" trick is difficult to use and read, many colors are unnecessary, and in the future structured log could help * It's difficult to add other log formats, eg: JSON format * The log outputer doesn't have full control of its goroutine, it's difficult to make outputer have advanced behaviors * The logs could be lost in some cases: eg: no Fatal error when using CLI. * Config options are passed by JSON, which is quite fragile. * INI package makes the KEY in `[log]` section visible in `[log.sub1]` and `[log.sub1.subA]`, this behavior is quite fragile and would cause more unclear problems, and there is no strong requirement to support `log.<mode>.<logger>` syntax. ## The new design See `logger.go` for documents. ## Screenshot <details> ![image](https://github.com/go-gitea/gitea/assets/2114189/4462d713-ba39-41f5-bb08-de912e67e1ff) ![image](https://github.com/go-gitea/gitea/assets/2114189/b188035e-f691-428b-8b2d-ff7b2199b2f9) ![image](https://github.com/go-gitea/gitea/assets/2114189/132e9745-1c3b-4e00-9e0d-15eaea495dee) </details> ## TODO * [x] add some new tests * [x] fix some tests * [x] test some sub-commands (manually ....) --------- Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: Giteabot <teabot@gitea.io>
1 year ago
Fix inconsistent user profile layout across tabs (#25625) Fix ::User Profile Page Project Tab Have Inconsistent Layout and Style Added the big_avator for consistency in the all header_items tabs. Fixes: #24871 > ### Description > in the user profile page the `Packages` and `Projects` tab have small icons for user but other tabs have bigger profile picture with user info: > > ### Screenshots > ### **For Packages And Projects:** > ![image](https://user-images.githubusercontent.com/25511175/240148601-2420d77b-ba25-4718-9ccb-c5d0d95e3079.png) > > ### **For Other Tabs:** > ![image](https://user-images.githubusercontent.com/25511175/240148461-ce9636b3-fe11-4c46-a230-30d83eee5947.png) > ## Before ![image](https://github.com/go-gitea/gitea/assets/80308335/975ad038-07ca-4b10-b75d-ccf259be7b9d) ## After changes Project View <img width="1394" alt="image" src="https://github.com/go-gitea/gitea/assets/80308335/95d181d7-8e61-496d-9899-7b825c91ad56"> Packages View <img width="1378" alt="image" src="https://github.com/go-gitea/gitea/assets/80308335/7f5fd60f-6b18-4fa8-8c56-7b0d45d1a610"> ## Org view for projects page <img width="1385" alt="image" src="https://github.com/go-gitea/gitea/assets/80308335/6400dc89-a5ae-4f0a-831b-5b6efa020d89"> ## Org view for packages page <img width="1387" alt="image" src="https://github.com/go-gitea/gitea/assets/80308335/4e1e9ffe-1e4b-4334-8657-de11b5fd31d0"> --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Giteabot <teabot@gitea.io> Co-authored-by: silverwind <me@silverwind.io>
11 months ago
Add Package Registry (#16510) * Added package store settings. * Added models. * Added generic package registry. * Added tests. * Added NuGet package registry. * Moved service index to api file. * Added NPM package registry. * Added Maven package registry. * Added PyPI package registry. * Summary is deprecated. * Changed npm name. * Sanitize project url. * Allow only scoped packages. * Added user interface. * Changed method name. * Added missing migration file. * Set page info. * Added documentation. * Added documentation links. * Fixed wrong error message. * Lint template files. * Fixed merge errors. * Fixed unit test storage path. * Switch to json module. * Added suggestions. * Added package webhook. * Add package api. * Fixed swagger file. * Fixed enum and comments. * Fixed NuGet pagination. * Print test names. * Added api tests. * Fixed access level. * Fix User unmarshal. * Added RubyGems package registry. * Fix lint. * Implemented io.Writer. * Added support for sha256/sha512 checksum files. * Improved maven-metadata.xml support. * Added support for symbol package uploads. * Added tests. * Added overview docs. * Added npm dependencies and keywords. * Added no-packages information. * Display file size. * Display asset count. * Fixed filter alignment. * Added package icons. * Formatted instructions. * Allow anonymous package downloads. * Fixed comments. * Fixed postgres test. * Moved file. * Moved models to models/packages. * Use correct error response format per client. * Use simpler search form. * Fixed IsProd. * Restructured data model. * Prevent empty filename. * Fix swagger. * Implemented user/org registry. * Implemented UI. * Use GetUserByIDCtx. * Use table for dependencies. * make svg * Added support for unscoped npm packages. * Add support for npm dist tags. * Added tests for npm tags. * Unlink packages if repository gets deleted. * Prevent user/org delete if a packages exist. * Use package unlink in repository service. * Added support for composer packages. * Restructured package docs. * Added missing tests. * Fixed generic content page. * Fixed docs. * Fixed swagger. * Added missing type. * Fixed ambiguous column. * Organize content store by sha256 hash. * Added admin package management. * Added support for sorting. * Add support for multiple identical versions/files. * Added missing repository unlink. * Added file properties. * make fmt * lint * Added Conan package registry. * Updated docs. * Unify package names. * Added swagger enum. * Use longer TEXT column type. * Removed version composite key. * Merged package and container registry. * Removed index. * Use dedicated package router. * Moved files to new location. * Updated docs. * Fixed JOIN order. * Fixed GROUP BY statement. * Fixed GROUP BY #2. * Added symbol server support. * Added more tests. * Set NOT NULL. * Added setting to disable package registries. * Moved auth into service. * refactor * Use ctx everywhere. * Added package cleanup task. * Changed packages path. * Added container registry. * Refactoring * Updated comparison. * Fix swagger. * Fixed table order. * Use token auth for npm routes. * Enabled ReverseProxy auth. * Added packages link for orgs. * Fixed anonymous org access. * Enable copy button for setup instructions. * Merge error * Added suggestions. * Fixed merge. * Handle "generic". * Added link for TODO. * Added suggestions. * Changed temporary buffer filename. * Added suggestions. * Apply suggestions from code review Co-authored-by: Thomas Boerger <thomas@webhippie.de> * Update docs/content/doc/packages/nuget.en-us.md Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Thomas Boerger <thomas@webhippie.de>
2 years ago
Implement actions (#21937) Close #13539. Co-authored by: @lunny @appleboy @fuxiaohei and others. Related projects: - https://gitea.com/gitea/actions-proto-def - https://gitea.com/gitea/actions-proto-go - https://gitea.com/gitea/act - https://gitea.com/gitea/act_runner ### Summary The target of this PR is to bring a basic implementation of "Actions", an internal CI/CD system of Gitea. That means even though it has been merged, the state of the feature is **EXPERIMENTAL**, and please note that: - It is disabled by default; - It shouldn't be used in a production environment currently; - It shouldn't be used in a public Gitea instance currently; - Breaking changes may be made before it's stable. **Please comment on #13539 if you have any different product design ideas**, all decisions reached there will be adopted here. But in this PR, we don't talk about **naming, feature-creep or alternatives**. ### ⚠️ Breaking `gitea-actions` will become a reserved user name. If a user with the name already exists in the database, it is recommended to rename it. ### Some important reviews - What is `DEFAULT_ACTIONS_URL` in `app.ini` for? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954 - Why the api for runners is not under the normal `/api/v1` prefix? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592 - Why DBFS? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178 - Why ignore events triggered by `gitea-actions` bot? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103 - Why there's no permission control for actions? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868 ### What it looks like <details> #### Manage runners <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png"> #### List runs <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png"> #### View logs <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png"> </details> ### How to try it <details> #### 1. Start Gitea Clone this branch and [install from source](https://docs.gitea.io/en-us/install-from-source). Add additional configurations in `app.ini` to enable Actions: ```ini [actions] ENABLED = true ``` Start it. If all is well, you'll see the management page of runners: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png"> #### 2. Start runner Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow the [README](https://gitea.com/gitea/act_runner/src/branch/main/README.md) to start it. If all is well, you'll see a new runner has been added: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png"> #### 3. Enable actions for a repo Create a new repo or open an existing one, check the `Actions` checkbox in settings and submit. <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png"> <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png"> If all is well, you'll see a new tab "Actions": <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png"> #### 4. Upload workflow files Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can follow the [quickstart](https://docs.github.com/en/actions/quickstart) of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions in most cases, you can use the same demo: ```yaml name: GitHub Actions Demo run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 on: [push] jobs: Explore-GitHub-Actions: runs-on: ubuntu-latest steps: - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code uses: actions/checkout@v3 - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner." - name: List files in the repository run: | ls ${{ github.workspace }} - run: echo "🍏 This job's status is ${{ job.status }}." ``` If all is well, you'll see a new run in `Actions` tab: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png"> #### 5. Check the logs of jobs Click a run and you'll see the logs: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png"> #### 6. Go on You can try more examples in [the documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) of GitHub Actions, then you might find a lot of bugs. Come on, PRs are welcome. </details> See also: [Feature Preview: Gitea Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/) --------- Co-authored-by: a1012112796 <1012112796@qq.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: ChristopherHX <christopher.homberger@web.de> Co-authored-by: John Olheiser <john.olheiser@gmail.com>
1 year ago
Implement actions (#21937) Close #13539. Co-authored by: @lunny @appleboy @fuxiaohei and others. Related projects: - https://gitea.com/gitea/actions-proto-def - https://gitea.com/gitea/actions-proto-go - https://gitea.com/gitea/act - https://gitea.com/gitea/act_runner ### Summary The target of this PR is to bring a basic implementation of "Actions", an internal CI/CD system of Gitea. That means even though it has been merged, the state of the feature is **EXPERIMENTAL**, and please note that: - It is disabled by default; - It shouldn't be used in a production environment currently; - It shouldn't be used in a public Gitea instance currently; - Breaking changes may be made before it's stable. **Please comment on #13539 if you have any different product design ideas**, all decisions reached there will be adopted here. But in this PR, we don't talk about **naming, feature-creep or alternatives**. ### ⚠️ Breaking `gitea-actions` will become a reserved user name. If a user with the name already exists in the database, it is recommended to rename it. ### Some important reviews - What is `DEFAULT_ACTIONS_URL` in `app.ini` for? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954 - Why the api for runners is not under the normal `/api/v1` prefix? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592 - Why DBFS? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178 - Why ignore events triggered by `gitea-actions` bot? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103 - Why there's no permission control for actions? - https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868 ### What it looks like <details> #### Manage runners <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png"> #### List runs <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png"> #### View logs <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png"> </details> ### How to try it <details> #### 1. Start Gitea Clone this branch and [install from source](https://docs.gitea.io/en-us/install-from-source). Add additional configurations in `app.ini` to enable Actions: ```ini [actions] ENABLED = true ``` Start it. If all is well, you'll see the management page of runners: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png"> #### 2. Start runner Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow the [README](https://gitea.com/gitea/act_runner/src/branch/main/README.md) to start it. If all is well, you'll see a new runner has been added: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png"> #### 3. Enable actions for a repo Create a new repo or open an existing one, check the `Actions` checkbox in settings and submit. <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png"> <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png"> If all is well, you'll see a new tab "Actions": <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png"> #### 4. Upload workflow files Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can follow the [quickstart](https://docs.github.com/en/actions/quickstart) of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions in most cases, you can use the same demo: ```yaml name: GitHub Actions Demo run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 on: [push] jobs: Explore-GitHub-Actions: runs-on: ubuntu-latest steps: - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code uses: actions/checkout@v3 - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner." - name: List files in the repository run: | ls ${{ github.workspace }} - run: echo "🍏 This job's status is ${{ job.status }}." ``` If all is well, you'll see a new run in `Actions` tab: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png"> #### 5. Check the logs of jobs Click a run and you'll see the logs: <img width="1792" alt="image" src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png"> #### 6. Go on You can try more examples in [the documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) of GitHub Actions, then you might find a lot of bugs. Come on, PRs are welcome. </details> See also: [Feature Preview: Gitea Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/) --------- Co-authored-by: a1012112796 <1012112796@qq.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: ChristopherHX <christopher.homberger@web.de> Co-authored-by: John Olheiser <john.olheiser@gmail.com>
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 year ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package user
  5. import (
  6. "context"
  7. "encoding/hex"
  8. "fmt"
  9. "net/url"
  10. "path/filepath"
  11. "strings"
  12. "time"
  13. _ "image/jpeg" // Needed for jpeg support
  14. "code.gitea.io/gitea/models/auth"
  15. "code.gitea.io/gitea/models/db"
  16. "code.gitea.io/gitea/modules/auth/openid"
  17. "code.gitea.io/gitea/modules/auth/password/hash"
  18. "code.gitea.io/gitea/modules/base"
  19. "code.gitea.io/gitea/modules/container"
  20. "code.gitea.io/gitea/modules/git"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/setting"
  23. "code.gitea.io/gitea/modules/structs"
  24. "code.gitea.io/gitea/modules/timeutil"
  25. "code.gitea.io/gitea/modules/util"
  26. "code.gitea.io/gitea/modules/validation"
  27. "xorm.io/builder"
  28. )
  29. // UserType defines the user type
  30. type UserType int //revive:disable-line:exported
  31. const (
  32. // UserTypeIndividual defines an individual user
  33. UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
  34. // UserTypeOrganization defines an organization
  35. UserTypeOrganization
  36. // UserTypeReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
  37. UserTypeUserReserved
  38. // UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
  39. UserTypeOrganizationReserved
  40. // UserTypeBot defines a bot user
  41. UserTypeBot
  42. // UserTypeRemoteUser defines a remote user for federated users
  43. UserTypeRemoteUser
  44. )
  45. const (
  46. // EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
  47. EmailNotificationsEnabled = "enabled"
  48. // EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned.
  49. EmailNotificationsOnMention = "onmention"
  50. // EmailNotificationsDisabled indicates that the user would not like to be notified via email.
  51. EmailNotificationsDisabled = "disabled"
  52. // EmailNotificationsAndYourOwn indicates that the user would like to receive all email notifications and your own
  53. EmailNotificationsAndYourOwn = "andyourown"
  54. )
  55. // User represents the object of individual and member of organization.
  56. type User struct {
  57. ID int64 `xorm:"pk autoincr"`
  58. LowerName string `xorm:"UNIQUE NOT NULL"`
  59. Name string `xorm:"UNIQUE NOT NULL"`
  60. FullName string
  61. // Email is the primary email address (to be used for communication)
  62. Email string `xorm:"NOT NULL"`
  63. KeepEmailPrivate bool
  64. EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
  65. Passwd string `xorm:"NOT NULL"`
  66. PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
  67. // MustChangePassword is an attribute that determines if a user
  68. // is to change their password after registration.
  69. MustChangePassword bool `xorm:"NOT NULL DEFAULT false"`
  70. LoginType auth.Type
  71. LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
  72. LoginName string
  73. Type UserType
  74. Location string
  75. Website string
  76. Rands string `xorm:"VARCHAR(32)"`
  77. Salt string `xorm:"VARCHAR(32)"`
  78. Language string `xorm:"VARCHAR(5)"`
  79. Description string
  80. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  81. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  82. LastLoginUnix timeutil.TimeStamp `xorm:"INDEX"`
  83. // Remember visibility choice for convenience, true for private
  84. LastRepoVisibility bool
  85. // Maximum repository creation limit, -1 means use global default
  86. MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"`
  87. // IsActive true: primary email is activated, user can access Web UI and Git SSH.
  88. // false: an inactive user can only log in Web UI for account operations (ex: activate the account by email), no other access.
  89. IsActive bool `xorm:"INDEX"`
  90. // the user is a Gitea admin, who can access all repositories and the admin pages.
  91. IsAdmin bool
  92. // true: the user is only allowed to see organizations/repositories that they has explicit rights to.
  93. // (ex: in private Gitea instances user won't be allowed to see even organizations/repositories that are set as public)
  94. IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
  95. AllowGitHook bool
  96. AllowImportLocal bool // Allow migrate repository by local path
  97. AllowCreateOrganization bool `xorm:"DEFAULT true"`
  98. // true: the user is not allowed to log in Web UI. Git/SSH access could still be allowed (please refer to Git/SSH access related code/documents)
  99. ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`
  100. // Avatar
  101. Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
  102. AvatarEmail string `xorm:"NOT NULL"`
  103. UseCustomAvatar bool
  104. // Counters
  105. NumFollowers int
  106. NumFollowing int `xorm:"NOT NULL DEFAULT 0"`
  107. NumStars int
  108. NumRepos int
  109. // For organization
  110. NumTeams int
  111. NumMembers int
  112. Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
  113. RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
  114. // Preferences
  115. DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
  116. Theme string `xorm:"NOT NULL DEFAULT ''"`
  117. KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
  118. }
  119. func init() {
  120. db.RegisterModel(new(User))
  121. }
  122. // SearchOrganizationsOptions options to filter organizations
  123. type SearchOrganizationsOptions struct {
  124. db.ListOptions
  125. All bool
  126. }
  127. func (u *User) LogString() string {
  128. if u == nil {
  129. return "<User nil>"
  130. }
  131. return fmt.Sprintf("<User %d:%s>", u.ID, u.Name)
  132. }
  133. // BeforeUpdate is invoked from XORM before updating this object.
  134. func (u *User) BeforeUpdate() {
  135. if u.MaxRepoCreation < -1 {
  136. u.MaxRepoCreation = -1
  137. }
  138. // Organization does not need email
  139. u.Email = strings.ToLower(u.Email)
  140. if !u.IsOrganization() {
  141. if len(u.AvatarEmail) == 0 {
  142. u.AvatarEmail = u.Email
  143. }
  144. }
  145. u.LowerName = strings.ToLower(u.Name)
  146. u.Location = base.TruncateString(u.Location, 255)
  147. u.Website = base.TruncateString(u.Website, 255)
  148. u.Description = base.TruncateString(u.Description, 255)
  149. }
  150. // AfterLoad is invoked from XORM after filling all the fields of this object.
  151. func (u *User) AfterLoad() {
  152. if u.Theme == "" {
  153. u.Theme = setting.UI.DefaultTheme
  154. }
  155. }
  156. // SetLastLogin set time to last login
  157. func (u *User) SetLastLogin() {
  158. u.LastLoginUnix = timeutil.TimeStampNow()
  159. }
  160. // UpdateUserDiffViewStyle updates the users diff view style
  161. func UpdateUserDiffViewStyle(ctx context.Context, u *User, style string) error {
  162. u.DiffViewStyle = style
  163. return UpdateUserCols(ctx, u, "diff_view_style")
  164. }
  165. // UpdateUserTheme updates a users' theme irrespective of the site wide theme
  166. func UpdateUserTheme(ctx context.Context, u *User, themeName string) error {
  167. u.Theme = themeName
  168. return UpdateUserCols(ctx, u, "theme")
  169. }
  170. // GetPlaceholderEmail returns an noreply email
  171. func (u *User) GetPlaceholderEmail() string {
  172. return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
  173. }
  174. // GetEmail returns an noreply email, if the user has set to keep his
  175. // email address private, otherwise the primary email address.
  176. func (u *User) GetEmail() string {
  177. if u.KeepEmailPrivate {
  178. return u.GetPlaceholderEmail()
  179. }
  180. return u.Email
  181. }
  182. // GetAllUsers returns a slice of all individual users found in DB.
  183. func GetAllUsers(ctx context.Context) ([]*User, error) {
  184. users := make([]*User, 0)
  185. return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users)
  186. }
  187. // IsLocal returns true if user login type is LoginPlain.
  188. func (u *User) IsLocal() bool {
  189. return u.LoginType <= auth.Plain
  190. }
  191. // IsOAuth2 returns true if user login type is LoginOAuth2.
  192. func (u *User) IsOAuth2() bool {
  193. return u.LoginType == auth.OAuth2
  194. }
  195. // MaxCreationLimit returns the number of repositories a user is allowed to create
  196. func (u *User) MaxCreationLimit() int {
  197. if u.MaxRepoCreation <= -1 {
  198. return setting.Repository.MaxCreationLimit
  199. }
  200. return u.MaxRepoCreation
  201. }
  202. // CanCreateRepo returns if user login can create a repository
  203. // NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised
  204. func (u *User) CanCreateRepo() bool {
  205. if u.IsAdmin {
  206. return true
  207. }
  208. if u.MaxRepoCreation <= -1 {
  209. if setting.Repository.MaxCreationLimit <= -1 {
  210. return true
  211. }
  212. return u.NumRepos < setting.Repository.MaxCreationLimit
  213. }
  214. return u.NumRepos < u.MaxRepoCreation
  215. }
  216. // CanCreateOrganization returns true if user can create organisation.
  217. func (u *User) CanCreateOrganization() bool {
  218. return u.IsAdmin || (u.AllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation)
  219. }
  220. // CanEditGitHook returns true if user can edit Git hooks.
  221. func (u *User) CanEditGitHook() bool {
  222. return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook)
  223. }
  224. // CanForkRepo returns if user login can fork a repository
  225. // It checks especially that the user can create repos, and potentially more
  226. func (u *User) CanForkRepo() bool {
  227. if setting.Repository.AllowForkWithoutMaximumLimit {
  228. return true
  229. }
  230. return u.CanCreateRepo()
  231. }
  232. // CanImportLocal returns true if user can migrate repository by local path.
  233. func (u *User) CanImportLocal() bool {
  234. if !setting.ImportLocalPaths || u == nil {
  235. return false
  236. }
  237. return u.IsAdmin || u.AllowImportLocal
  238. }
  239. // DashboardLink returns the user dashboard page link.
  240. func (u *User) DashboardLink() string {
  241. if u.IsOrganization() {
  242. return u.OrganisationLink() + "/dashboard"
  243. }
  244. return setting.AppSubURL + "/"
  245. }
  246. // HomeLink returns the user or organization home page link.
  247. func (u *User) HomeLink() string {
  248. return setting.AppSubURL + "/" + url.PathEscape(u.Name)
  249. }
  250. // HTMLURL returns the user or organization's full link.
  251. func (u *User) HTMLURL() string {
  252. return setting.AppURL + url.PathEscape(u.Name)
  253. }
  254. // OrganisationLink returns the organization sub page link.
  255. func (u *User) OrganisationLink() string {
  256. return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
  257. }
  258. // GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
  259. func (u *User) GenerateEmailActivateCode(email string) string {
  260. code := base.CreateTimeLimitCode(
  261. fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
  262. setting.Service.ActiveCodeLives, nil)
  263. // Add tail hex username
  264. code += hex.EncodeToString([]byte(u.LowerName))
  265. return code
  266. }
  267. // GetUserFollowers returns range of user's followers.
  268. func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
  269. sess := db.GetEngine(ctx).
  270. Select("`user`.*").
  271. Join("LEFT", "follow", "`user`.id=follow.user_id").
  272. Where("follow.follow_id=?", u.ID).
  273. And("`user`.type=?", UserTypeIndividual).
  274. And(isUserVisibleToViewerCond(viewer))
  275. if listOptions.Page != 0 {
  276. sess = db.SetSessionPagination(sess, &listOptions)
  277. users := make([]*User, 0, listOptions.PageSize)
  278. count, err := sess.FindAndCount(&users)
  279. return users, count, err
  280. }
  281. users := make([]*User, 0, 8)
  282. count, err := sess.FindAndCount(&users)
  283. return users, count, err
  284. }
  285. // GetUserFollowing returns range of user's following.
  286. func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
  287. sess := db.GetEngine(ctx).
  288. Select("`user`.*").
  289. Join("LEFT", "follow", "`user`.id=follow.follow_id").
  290. Where("follow.user_id=?", u.ID).
  291. And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization).
  292. And(isUserVisibleToViewerCond(viewer))
  293. if listOptions.Page != 0 {
  294. sess = db.SetSessionPagination(sess, &listOptions)
  295. users := make([]*User, 0, listOptions.PageSize)
  296. count, err := sess.FindAndCount(&users)
  297. return users, count, err
  298. }
  299. users := make([]*User, 0, 8)
  300. count, err := sess.FindAndCount(&users)
  301. return users, count, err
  302. }
  303. // NewGitSig generates and returns the signature of given user.
  304. func (u *User) NewGitSig() *git.Signature {
  305. return &git.Signature{
  306. Name: u.GitName(),
  307. Email: u.GetEmail(),
  308. When: time.Now(),
  309. }
  310. }
  311. // SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO
  312. // change passwd, salt and passwd_hash_algo fields
  313. func (u *User) SetPassword(passwd string) (err error) {
  314. if len(passwd) == 0 {
  315. u.Passwd = ""
  316. u.Salt = ""
  317. u.PasswdHashAlgo = ""
  318. return nil
  319. }
  320. if u.Salt, err = GetUserSalt(); err != nil {
  321. return err
  322. }
  323. if u.Passwd, err = hash.Parse(setting.PasswordHashAlgo).Hash(passwd, u.Salt); err != nil {
  324. return err
  325. }
  326. u.PasswdHashAlgo = setting.PasswordHashAlgo
  327. return nil
  328. }
  329. // ValidatePassword checks if the given password matches the one belonging to the user.
  330. func (u *User) ValidatePassword(passwd string) bool {
  331. return hash.Parse(u.PasswdHashAlgo).VerifyPassword(passwd, u.Passwd, u.Salt)
  332. }
  333. // IsPasswordSet checks if the password is set or left empty
  334. func (u *User) IsPasswordSet() bool {
  335. return len(u.Passwd) != 0
  336. }
  337. // IsOrganization returns true if user is actually a organization.
  338. func (u *User) IsOrganization() bool {
  339. return u.Type == UserTypeOrganization
  340. }
  341. // IsIndividual returns true if user is actually a individual user.
  342. func (u *User) IsIndividual() bool {
  343. return u.Type == UserTypeIndividual
  344. }
  345. // IsBot returns whether or not the user is of type bot
  346. func (u *User) IsBot() bool {
  347. return u.Type == UserTypeBot
  348. }
  349. // DisplayName returns full name if it's not empty,
  350. // returns username otherwise.
  351. func (u *User) DisplayName() string {
  352. trimmed := strings.TrimSpace(u.FullName)
  353. if len(trimmed) > 0 {
  354. return trimmed
  355. }
  356. return u.Name
  357. }
  358. // GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set,
  359. // returns username otherwise.
  360. func (u *User) GetDisplayName() string {
  361. if setting.UI.DefaultShowFullName {
  362. trimmed := strings.TrimSpace(u.FullName)
  363. if len(trimmed) > 0 {
  364. return trimmed
  365. }
  366. }
  367. return u.Name
  368. }
  369. func gitSafeName(name string) string {
  370. return strings.TrimSpace(strings.NewReplacer("\n", "", "<", "", ">", "").Replace(name))
  371. }
  372. // GitName returns a git safe name
  373. func (u *User) GitName() string {
  374. gitName := gitSafeName(u.FullName)
  375. if len(gitName) > 0 {
  376. return gitName
  377. }
  378. // Although u.Name should be safe if created in our system
  379. // LDAP users may have bad names
  380. gitName = gitSafeName(u.Name)
  381. if len(gitName) > 0 {
  382. return gitName
  383. }
  384. // Totally pathological name so it's got to be:
  385. return fmt.Sprintf("user-%d", u.ID)
  386. }
  387. // ShortName ellipses username to length
  388. func (u *User) ShortName(length int) string {
  389. if setting.UI.DefaultShowFullName && len(u.FullName) > 0 {
  390. return base.EllipsisString(u.FullName, length)
  391. }
  392. return base.EllipsisString(u.Name, length)
  393. }
  394. // IsMailable checks if a user is eligible
  395. // to receive emails.
  396. func (u *User) IsMailable() bool {
  397. return u.IsActive
  398. }
  399. // EmailNotifications returns the User's email notification preference
  400. func (u *User) EmailNotifications() string {
  401. return u.EmailNotificationsPreference
  402. }
  403. // SetEmailNotifications sets the user's email notification preference
  404. func SetEmailNotifications(ctx context.Context, u *User, set string) error {
  405. u.EmailNotificationsPreference = set
  406. if err := UpdateUserCols(ctx, u, "email_notifications_preference"); err != nil {
  407. log.Error("SetEmailNotifications: %v", err)
  408. return err
  409. }
  410. return nil
  411. }
  412. // IsUserExist checks if given user name exist,
  413. // the user name should be noncased unique.
  414. // If uid is presented, then check will rule out that one,
  415. // it is used when update a user name in settings page.
  416. func IsUserExist(ctx context.Context, uid int64, name string) (bool, error) {
  417. if len(name) == 0 {
  418. return false, nil
  419. }
  420. return db.GetEngine(ctx).
  421. Where("id!=?", uid).
  422. Get(&User{LowerName: strings.ToLower(name)})
  423. }
  424. // Note: As of the beginning of 2022, it is recommended to use at least
  425. // 64 bits of salt, but NIST is already recommending to use to 128 bits.
  426. // (16 bytes = 16 * 8 = 128 bits)
  427. const SaltByteLength = 16
  428. // GetUserSalt returns a random user salt token.
  429. func GetUserSalt() (string, error) {
  430. rBytes, err := util.CryptoRandomBytes(SaltByteLength)
  431. if err != nil {
  432. return "", err
  433. }
  434. // Returns a 32 bytes long string.
  435. return hex.EncodeToString(rBytes), nil
  436. }
  437. var (
  438. reservedUsernames = []string{
  439. ".",
  440. "..",
  441. ".well-known",
  442. "admin",
  443. "api",
  444. "assets",
  445. "attachments",
  446. "avatar",
  447. "avatars",
  448. "captcha",
  449. "commits",
  450. "debug",
  451. "error",
  452. "explore",
  453. "favicon.ico",
  454. "ghost",
  455. "issues",
  456. "login",
  457. "manifest.json",
  458. "metrics",
  459. "milestones",
  460. "new",
  461. "notifications",
  462. "org",
  463. "pulls",
  464. "raw",
  465. "repo",
  466. "repo-avatars",
  467. "robots.txt",
  468. "search",
  469. "serviceworker.js",
  470. "ssh_info",
  471. "swagger.v1.json",
  472. "user",
  473. "v2",
  474. "gitea-actions",
  475. }
  476. // DON'T ADD ANY NEW STUFF, WE SOLVE THIS WITH `/user/{obj}` PATHS!
  477. reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"}
  478. )
  479. // IsUsableUsername returns an error when a username is reserved
  480. func IsUsableUsername(name string) error {
  481. // Validate username make sure it satisfies requirement.
  482. if !validation.IsValidUsername(name) {
  483. // Note: usually this error is normally caught up earlier in the UI
  484. return db.ErrNameCharsNotAllowed{Name: name}
  485. }
  486. return db.IsUsableName(reservedUsernames, reservedUserPatterns, name)
  487. }
  488. // CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation
  489. type CreateUserOverwriteOptions struct {
  490. KeepEmailPrivate util.OptionalBool
  491. Visibility *structs.VisibleType
  492. AllowCreateOrganization util.OptionalBool
  493. EmailNotificationsPreference *string
  494. MaxRepoCreation *int
  495. Theme *string
  496. IsRestricted util.OptionalBool
  497. IsActive util.OptionalBool
  498. }
  499. // CreateUser creates record of a new user.
  500. func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
  501. if err = IsUsableUsername(u.Name); err != nil {
  502. return err
  503. }
  504. // set system defaults
  505. u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
  506. u.Visibility = setting.Service.DefaultUserVisibilityMode
  507. u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
  508. u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
  509. u.MaxRepoCreation = -1
  510. u.Theme = setting.UI.DefaultTheme
  511. u.IsRestricted = setting.Service.DefaultUserIsRestricted
  512. u.IsActive = !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm)
  513. // Ensure consistency of the dates.
  514. if u.UpdatedUnix < u.CreatedUnix {
  515. u.UpdatedUnix = u.CreatedUnix
  516. }
  517. // overwrite defaults if set
  518. if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
  519. overwrite := overwriteDefault[0]
  520. if !overwrite.KeepEmailPrivate.IsNone() {
  521. u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue()
  522. }
  523. if overwrite.Visibility != nil {
  524. u.Visibility = *overwrite.Visibility
  525. }
  526. if !overwrite.AllowCreateOrganization.IsNone() {
  527. u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue()
  528. }
  529. if overwrite.EmailNotificationsPreference != nil {
  530. u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
  531. }
  532. if overwrite.MaxRepoCreation != nil {
  533. u.MaxRepoCreation = *overwrite.MaxRepoCreation
  534. }
  535. if overwrite.Theme != nil {
  536. u.Theme = *overwrite.Theme
  537. }
  538. if !overwrite.IsRestricted.IsNone() {
  539. u.IsRestricted = overwrite.IsRestricted.IsTrue()
  540. }
  541. if !overwrite.IsActive.IsNone() {
  542. u.IsActive = overwrite.IsActive.IsTrue()
  543. }
  544. }
  545. // validate data
  546. if err := ValidateUser(u); err != nil {
  547. return err
  548. }
  549. if err := ValidateEmail(u.Email); err != nil {
  550. return err
  551. }
  552. ctx, committer, err := db.TxContext(ctx)
  553. if err != nil {
  554. return err
  555. }
  556. defer committer.Close()
  557. isExist, err := IsUserExist(ctx, 0, u.Name)
  558. if err != nil {
  559. return err
  560. } else if isExist {
  561. return ErrUserAlreadyExist{u.Name}
  562. }
  563. isExist, err = IsEmailUsed(ctx, u.Email)
  564. if err != nil {
  565. return err
  566. } else if isExist {
  567. return ErrEmailAlreadyUsed{
  568. Email: u.Email,
  569. }
  570. }
  571. // prepare for database
  572. u.LowerName = strings.ToLower(u.Name)
  573. u.AvatarEmail = u.Email
  574. if u.Rands, err = GetUserSalt(); err != nil {
  575. return err
  576. }
  577. if err = u.SetPassword(u.Passwd); err != nil {
  578. return err
  579. }
  580. // save changes to database
  581. if err = DeleteUserRedirect(ctx, u.Name); err != nil {
  582. return err
  583. }
  584. if u.CreatedUnix == 0 {
  585. // Caller expects auto-time for creation & update timestamps.
  586. err = db.Insert(ctx, u)
  587. } else {
  588. // Caller sets the timestamps themselves. They are responsible for ensuring
  589. // both `CreatedUnix` and `UpdatedUnix` are set appropriately.
  590. _, err = db.GetEngine(ctx).NoAutoTime().Insert(u)
  591. }
  592. if err != nil {
  593. return err
  594. }
  595. // insert email address
  596. if err := db.Insert(ctx, &EmailAddress{
  597. UID: u.ID,
  598. Email: u.Email,
  599. LowerEmail: strings.ToLower(u.Email),
  600. IsActivated: u.IsActive,
  601. IsPrimary: true,
  602. }); err != nil {
  603. return err
  604. }
  605. return committer.Commit()
  606. }
  607. // IsLastAdminUser check whether user is the last admin
  608. func IsLastAdminUser(ctx context.Context, user *User) bool {
  609. if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: util.OptionalBoolTrue}) <= 1 {
  610. return true
  611. }
  612. return false
  613. }
  614. // CountUserFilter represent optional filters for CountUsers
  615. type CountUserFilter struct {
  616. LastLoginSince *int64
  617. IsAdmin util.OptionalBool
  618. }
  619. // CountUsers returns number of users.
  620. func CountUsers(ctx context.Context, opts *CountUserFilter) int64 {
  621. return countUsers(ctx, opts)
  622. }
  623. func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
  624. sess := db.GetEngine(ctx)
  625. cond := builder.NewCond()
  626. cond = cond.And(builder.Eq{"type": UserTypeIndividual})
  627. if opts != nil {
  628. if opts.LastLoginSince != nil {
  629. cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince})
  630. }
  631. if !opts.IsAdmin.IsNone() {
  632. cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
  633. }
  634. }
  635. count, err := sess.Where(cond).Count(new(User))
  636. if err != nil {
  637. log.Error("user.countUsers: %v", err)
  638. }
  639. return count
  640. }
  641. // GetVerifyUser get user by verify code
  642. func GetVerifyUser(ctx context.Context, code string) (user *User) {
  643. if len(code) <= base.TimeLimitCodeLength {
  644. return nil
  645. }
  646. // use tail hex username query user
  647. hexStr := code[base.TimeLimitCodeLength:]
  648. if b, err := hex.DecodeString(hexStr); err == nil {
  649. if user, err = GetUserByName(ctx, string(b)); user != nil {
  650. return user
  651. }
  652. log.Error("user.getVerifyUser: %v", err)
  653. }
  654. return nil
  655. }
  656. // VerifyUserActiveCode verifies active code when active account
  657. func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
  658. minutes := setting.Service.ActiveCodeLives
  659. if user = GetVerifyUser(ctx, code); user != nil {
  660. // time limit code
  661. prefix := code[:base.TimeLimitCodeLength]
  662. data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
  663. if base.VerifyTimeLimitCode(data, minutes, prefix) {
  664. return user
  665. }
  666. }
  667. return nil
  668. }
  669. // checkDupEmail checks whether there are the same email with the user
  670. func checkDupEmail(ctx context.Context, u *User) error {
  671. u.Email = strings.ToLower(u.Email)
  672. has, err := db.GetEngine(ctx).
  673. Where("id!=?", u.ID).
  674. And("type=?", u.Type).
  675. And("email=?", u.Email).
  676. Get(new(User))
  677. if err != nil {
  678. return err
  679. } else if has {
  680. return ErrEmailAlreadyUsed{
  681. Email: u.Email,
  682. }
  683. }
  684. return nil
  685. }
  686. // ValidateUser check if user is valid to insert / update into database
  687. func ValidateUser(u *User, cols ...string) error {
  688. if len(cols) == 0 || util.SliceContainsString(cols, "visibility", true) {
  689. if !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(u.Visibility) && !u.IsOrganization() {
  690. return fmt.Errorf("visibility Mode not allowed: %s", u.Visibility.String())
  691. }
  692. }
  693. if len(cols) == 0 || util.SliceContainsString(cols, "email", true) {
  694. u.Email = strings.ToLower(u.Email)
  695. if err := ValidateEmail(u.Email); err != nil {
  696. return err
  697. }
  698. }
  699. return nil
  700. }
  701. // UpdateUser updates user's information.
  702. func UpdateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error {
  703. err := ValidateUser(u, cols...)
  704. if err != nil {
  705. return err
  706. }
  707. e := db.GetEngine(ctx)
  708. if changePrimaryEmail {
  709. var emailAddress EmailAddress
  710. has, err := e.Where("lower_email=?", strings.ToLower(u.Email)).Get(&emailAddress)
  711. if err != nil {
  712. return err
  713. }
  714. if has && emailAddress.UID != u.ID {
  715. return ErrEmailAlreadyUsed{
  716. Email: u.Email,
  717. }
  718. }
  719. // 1. Update old primary email
  720. if _, err = e.Where("uid=? AND is_primary=?", u.ID, true).Cols("is_primary").Update(&EmailAddress{
  721. IsPrimary: false,
  722. }); err != nil {
  723. return err
  724. }
  725. if !has {
  726. emailAddress.Email = u.Email
  727. emailAddress.UID = u.ID
  728. emailAddress.IsActivated = true
  729. emailAddress.IsPrimary = true
  730. if _, err := e.Insert(&emailAddress); err != nil {
  731. return err
  732. }
  733. } else if _, err := e.ID(emailAddress.ID).Cols("is_primary").Update(&EmailAddress{
  734. IsPrimary: true,
  735. }); err != nil {
  736. return err
  737. }
  738. } else if !u.IsOrganization() { // check if primary email in email_address table
  739. primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{})
  740. if err != nil {
  741. return err
  742. }
  743. if !primaryEmailExist {
  744. if _, err := e.Insert(&EmailAddress{
  745. Email: u.Email,
  746. UID: u.ID,
  747. IsActivated: true,
  748. IsPrimary: true,
  749. }); err != nil {
  750. return err
  751. }
  752. }
  753. }
  754. if len(cols) == 0 {
  755. _, err = e.ID(u.ID).AllCols().Update(u)
  756. } else {
  757. _, err = e.ID(u.ID).Cols(cols...).Update(u)
  758. }
  759. return err
  760. }
  761. // UpdateUserCols update user according special columns
  762. func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
  763. if err := ValidateUser(u, cols...); err != nil {
  764. return err
  765. }
  766. _, err := db.GetEngine(ctx).ID(u.ID).Cols(cols...).Update(u)
  767. return err
  768. }
  769. // UpdateUserSetting updates user's settings.
  770. func UpdateUserSetting(ctx context.Context, u *User) (err error) {
  771. ctx, committer, err := db.TxContext(ctx)
  772. if err != nil {
  773. return err
  774. }
  775. defer committer.Close()
  776. if !u.IsOrganization() {
  777. if err = checkDupEmail(ctx, u); err != nil {
  778. return err
  779. }
  780. }
  781. if err = UpdateUser(ctx, u, false); err != nil {
  782. return err
  783. }
  784. return committer.Commit()
  785. }
  786. // GetInactiveUsers gets all inactive users
  787. func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
  788. var cond builder.Cond = builder.Eq{"is_active": false}
  789. if olderThan > 0 {
  790. cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
  791. }
  792. users := make([]*User, 0, 10)
  793. return users, db.GetEngine(ctx).
  794. Where(cond).
  795. Find(&users)
  796. }
  797. // UserPath returns the path absolute path of user repositories.
  798. func UserPath(userName string) string { //revive:disable-line:exported
  799. return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
  800. }
  801. // GetUserByID returns the user object by given ID if exists.
  802. func GetUserByID(ctx context.Context, id int64) (*User, error) {
  803. u := new(User)
  804. has, err := db.GetEngine(ctx).ID(id).Get(u)
  805. if err != nil {
  806. return nil, err
  807. } else if !has {
  808. return nil, ErrUserNotExist{id, "", 0}
  809. }
  810. return u, nil
  811. }
  812. // GetUserByIDs returns the user objects by given IDs if exists.
  813. func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
  814. users := make([]*User, 0, len(ids))
  815. err := db.GetEngine(ctx).In("id", ids).
  816. Table("user").
  817. Find(&users)
  818. return users, err
  819. }
  820. // GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
  821. func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
  822. switch id {
  823. case -1:
  824. return NewGhostUser(), nil
  825. case ActionsUserID:
  826. return NewActionsUser(), nil
  827. case 0:
  828. return nil, ErrUserNotExist{}
  829. default:
  830. return GetUserByID(ctx, id)
  831. }
  832. }
  833. // GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0
  834. func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
  835. uniqueIDs := container.SetOf(ids...)
  836. users := make([]*User, 0, len(ids))
  837. _ = uniqueIDs.Remove(0)
  838. if uniqueIDs.Remove(-1) {
  839. users = append(users, NewGhostUser())
  840. }
  841. if uniqueIDs.Remove(ActionsUserID) {
  842. users = append(users, NewActionsUser())
  843. }
  844. res, err := GetUserByIDs(ctx, uniqueIDs.Values())
  845. if err != nil {
  846. return nil, err
  847. }
  848. users = append(users, res...)
  849. return users, nil
  850. }
  851. // GetUserByNameCtx returns user by given name.
  852. func GetUserByName(ctx context.Context, name string) (*User, error) {
  853. if len(name) == 0 {
  854. return nil, ErrUserNotExist{0, name, 0}
  855. }
  856. u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual}
  857. has, err := db.GetEngine(ctx).Get(u)
  858. if err != nil {
  859. return nil, err
  860. } else if !has {
  861. return nil, ErrUserNotExist{0, name, 0}
  862. }
  863. return u, nil
  864. }
  865. // GetUserEmailsByNames returns a list of e-mails corresponds to names of users
  866. // that have their email notifications set to enabled or onmention.
  867. func GetUserEmailsByNames(ctx context.Context, names []string) []string {
  868. mails := make([]string, 0, len(names))
  869. for _, name := range names {
  870. u, err := GetUserByName(ctx, name)
  871. if err != nil {
  872. continue
  873. }
  874. if u.IsMailable() && u.EmailNotifications() != EmailNotificationsDisabled {
  875. mails = append(mails, u.Email)
  876. }
  877. }
  878. return mails
  879. }
  880. // GetMaileableUsersByIDs gets users from ids, but only if they can receive mails
  881. func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) {
  882. if len(ids) == 0 {
  883. return nil, nil
  884. }
  885. ous := make([]*User, 0, len(ids))
  886. if isMention {
  887. return ous, db.GetEngine(ctx).
  888. In("id", ids).
  889. Where("`type` = ?", UserTypeIndividual).
  890. And("`prohibit_login` = ?", false).
  891. And("`is_active` = ?", true).
  892. In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsOnMention, EmailNotificationsAndYourOwn).
  893. Find(&ous)
  894. }
  895. return ous, db.GetEngine(ctx).
  896. In("id", ids).
  897. Where("`type` = ?", UserTypeIndividual).
  898. And("`prohibit_login` = ?", false).
  899. And("`is_active` = ?", true).
  900. In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsAndYourOwn).
  901. Find(&ous)
  902. }
  903. // GetUserNamesByIDs returns usernames for all resolved users from a list of Ids.
  904. func GetUserNamesByIDs(ctx context.Context, ids []int64) ([]string, error) {
  905. unames := make([]string, 0, len(ids))
  906. err := db.GetEngine(ctx).In("id", ids).
  907. Table("user").
  908. Asc("name").
  909. Cols("name").
  910. Find(&unames)
  911. return unames, err
  912. }
  913. // GetUserNameByID returns username for the id
  914. func GetUserNameByID(ctx context.Context, id int64) (string, error) {
  915. var name string
  916. has, err := db.GetEngine(ctx).Table("user").Where("id = ?", id).Cols("name").Get(&name)
  917. if err != nil {
  918. return "", err
  919. }
  920. if has {
  921. return name, nil
  922. }
  923. return "", nil
  924. }
  925. // GetUserIDsByNames returns a slice of ids corresponds to names.
  926. func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bool) ([]int64, error) {
  927. ids := make([]int64, 0, len(names))
  928. for _, name := range names {
  929. u, err := GetUserByName(ctx, name)
  930. if err != nil {
  931. if ignoreNonExistent {
  932. continue
  933. } else {
  934. return nil, err
  935. }
  936. }
  937. ids = append(ids, u.ID)
  938. }
  939. return ids, nil
  940. }
  941. // GetUsersBySource returns a list of Users for a login source
  942. func GetUsersBySource(ctx context.Context, s *auth.Source) ([]*User, error) {
  943. var users []*User
  944. err := db.GetEngine(ctx).Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users)
  945. return users, err
  946. }
  947. // UserCommit represents a commit with validation of user.
  948. type UserCommit struct { //revive:disable-line:exported
  949. User *User
  950. *git.Commit
  951. }
  952. // ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user.
  953. func ValidateCommitWithEmail(ctx context.Context, c *git.Commit) *User {
  954. if c.Author == nil {
  955. return nil
  956. }
  957. u, err := GetUserByEmail(ctx, c.Author.Email)
  958. if err != nil {
  959. return nil
  960. }
  961. return u
  962. }
  963. // ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
  964. func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []*UserCommit {
  965. var (
  966. emails = make(map[string]*User)
  967. newCommits = make([]*UserCommit, 0, len(oldCommits))
  968. )
  969. for _, c := range oldCommits {
  970. var u *User
  971. if c.Author != nil {
  972. if v, ok := emails[c.Author.Email]; !ok {
  973. u, _ = GetUserByEmail(ctx, c.Author.Email)
  974. emails[c.Author.Email] = u
  975. } else {
  976. u = v
  977. }
  978. }
  979. newCommits = append(newCommits, &UserCommit{
  980. User: u,
  981. Commit: c,
  982. })
  983. }
  984. return newCommits
  985. }
  986. // GetUserByEmail returns the user object by given e-mail if exists.
  987. func GetUserByEmail(ctx context.Context, email string) (*User, error) {
  988. if len(email) == 0 {
  989. return nil, ErrUserNotExist{0, email, 0}
  990. }
  991. email = strings.ToLower(email)
  992. // Otherwise, check in alternative list for activated email addresses
  993. emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
  994. has, err := db.GetEngine(ctx).Get(emailAddress)
  995. if err != nil {
  996. return nil, err
  997. }
  998. if has {
  999. return GetUserByID(ctx, emailAddress.UID)
  1000. }
  1001. // Finally, if email address is the protected email address:
  1002. if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
  1003. username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
  1004. user := &User{}
  1005. has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user)
  1006. if err != nil {
  1007. return nil, err
  1008. }
  1009. if has {
  1010. return user, nil
  1011. }
  1012. }
  1013. return nil, ErrUserNotExist{0, email, 0}
  1014. }
  1015. // GetUser checks if a user already exists
  1016. func GetUser(ctx context.Context, user *User) (bool, error) {
  1017. return db.GetEngine(ctx).Get(user)
  1018. }
  1019. // GetUserByOpenID returns the user object by given OpenID if exists.
  1020. func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
  1021. if len(uri) == 0 {
  1022. return nil, ErrUserNotExist{0, uri, 0}
  1023. }
  1024. uri, err := openid.Normalize(uri)
  1025. if err != nil {
  1026. return nil, err
  1027. }
  1028. log.Trace("Normalized OpenID URI: " + uri)
  1029. // Otherwise, check in openid table
  1030. oid := &UserOpenID{}
  1031. has, err := db.GetEngine(ctx).Where("uri=?", uri).Get(oid)
  1032. if err != nil {
  1033. return nil, err
  1034. }
  1035. if has {
  1036. return GetUserByID(ctx, oid.UID)
  1037. }
  1038. return nil, ErrUserNotExist{0, uri, 0}
  1039. }
  1040. // GetAdminUser returns the first administrator
  1041. func GetAdminUser(ctx context.Context) (*User, error) {
  1042. var admin User
  1043. has, err := db.GetEngine(ctx).
  1044. Where("is_admin=?", true).
  1045. Asc("id"). // Reliably get the admin with the lowest ID.
  1046. Get(&admin)
  1047. if err != nil {
  1048. return nil, err
  1049. } else if !has {
  1050. return nil, ErrUserNotExist{}
  1051. }
  1052. return &admin, nil
  1053. }
  1054. func isUserVisibleToViewerCond(viewer *User) builder.Cond {
  1055. if viewer != nil && viewer.IsAdmin {
  1056. return builder.NewCond()
  1057. }
  1058. if viewer == nil || viewer.IsRestricted {
  1059. return builder.Eq{
  1060. "`user`.visibility": structs.VisibleTypePublic,
  1061. }
  1062. }
  1063. return builder.Neq{
  1064. "`user`.visibility": structs.VisibleTypePrivate,
  1065. }.Or(
  1066. // viewer self
  1067. builder.Eq{"`user`.id": viewer.ID},
  1068. // viewer's following
  1069. builder.In("`user`.id",
  1070. builder.
  1071. Select("`follow`.user_id").
  1072. From("follow").
  1073. Where(builder.Eq{"`follow`.follow_id": viewer.ID})),
  1074. // viewer's org user
  1075. builder.In("`user`.id",
  1076. builder.
  1077. Select("`team_user`.uid").
  1078. From("team_user").
  1079. Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id").
  1080. Where(builder.Eq{"`t2`.uid": viewer.ID})),
  1081. // viewer's org
  1082. builder.In("`user`.id",
  1083. builder.
  1084. Select("`team_user`.org_id").
  1085. From("team_user").
  1086. Where(builder.Eq{"`team_user`.uid": viewer.ID})))
  1087. }
  1088. // IsUserVisibleToViewer check if viewer is able to see user profile
  1089. func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
  1090. if viewer != nil && (viewer.IsAdmin || viewer.ID == u.ID) {
  1091. return true
  1092. }
  1093. switch u.Visibility {
  1094. case structs.VisibleTypePublic:
  1095. return true
  1096. case structs.VisibleTypeLimited:
  1097. if viewer == nil || viewer.IsRestricted {
  1098. return false
  1099. }
  1100. return true
  1101. case structs.VisibleTypePrivate:
  1102. if viewer == nil || viewer.IsRestricted {
  1103. return false
  1104. }
  1105. // If they follow - they see each over
  1106. follower := IsFollowing(ctx, u.ID, viewer.ID)
  1107. if follower {
  1108. return true
  1109. }
  1110. // Now we need to check if they in some organization together
  1111. count, err := db.GetEngine(ctx).Table("team_user").
  1112. Where(
  1113. builder.And(
  1114. builder.Eq{"uid": viewer.ID},
  1115. builder.Or(
  1116. builder.Eq{"org_id": u.ID},
  1117. builder.In("org_id",
  1118. builder.Select("org_id").
  1119. From("team_user", "t2").
  1120. Where(builder.Eq{"uid": u.ID}))))).
  1121. Count()
  1122. if err != nil {
  1123. return false
  1124. }
  1125. if count == 0 {
  1126. // No common organization
  1127. return false
  1128. }
  1129. // they are in an organization together
  1130. return true
  1131. }
  1132. return false
  1133. }
  1134. // CountWrongUserType count OrgUser who have wrong type
  1135. func CountWrongUserType(ctx context.Context) (int64, error) {
  1136. return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User))
  1137. }
  1138. // FixWrongUserType fix OrgUser who have wrong type
  1139. func FixWrongUserType(ctx context.Context) (int64, error) {
  1140. return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1})
  1141. }
  1142. func GetOrderByName() string {
  1143. if setting.UI.DefaultShowFullName {
  1144. return "full_name, name"
  1145. }
  1146. return "name"
  1147. }