This feature was originally experimental. We made it baseline in #29572, however this was met with some pushback, so we have decided to make it experimental again.
The original PR couldn't be reverted, but this one basically does the same by cherry-picking all the changes back.
This PR does some things to the about page:
- Hide the stat if there are 0 admins/moderators.
- Count admins and moderators separately, so a user who is both will add
to both counts.
- A user who is both will still show up as a moderator, even if `admins`
is hidden.
Currently, if creating an API key in "granular" mode, and not selecting any scopes, a globally scoped API key is created. This can be surprising and is not ideal. Having a key with no scopes isn't useful in the first place, so this PR adds client- and server side validations to check that at least one scope is selected if using "granular" mode.
Translation overrides can be marked as "invalid interpolation keys" or "outdated" if the original translation is changed. We run a job every hour to check for this. We also have an admin problem check for it.
The problem is we don't refresh this status when an admin updates the override. So even if the invalid keys are removed, the override will still show up under the "invalid" filter.
There's a similar situation with the "outdated" status. The admin is shown a prompt which they can dismiss, which in turn updates the status, but updating the translation should also count as "addressing" it.
This PR runs a refresh on the override status when updating.
In a previous PR, I introduced this system spec that checks that a sidebar link is auto-generated for certain plugins.
This causes problems, because the core test suite can be run with plugins either enabled or disabled, causing flaky tests.
Currently, the markdown for uploads is counted towards post minimum length requirements. This change introduces a site setting `prevent_uploads_only_posts` which can be flipped to exclude upload segments from the calculation.
In the current admin index page, all plugins show up as tabs. This includes plugins with auto-generated config routes.
This changes the tabs to include only plugins with custom UIs.
In #31012 we deprecated this nav mode option when registering a plugin in a backwards compatible way.
We have now removed its use in all relevant plugins and should be ready to completely remove it from core.
This change adds a sidebar link for each plugin that fulfils the following criteria:
- Does not have an explicit admin route defined in the plugin.
- Has at least one site setting (not including enabled/disabled.)
That sidebar link leads to the automatically generated plugin show settings page.
We used this flag for experimenting with admin plugin sidebars. We have now settled on a tabbed layout, and this is no longer needed.
This PR simply ignores the flag in a backwards-compatible way, so we can discontinue usage in plugins and then remove the backwards-compatibility in core.
This fixes two minor problems on the admin webhooks page.
- Wrong key used for edit button title in listing.
- Duplicated use of `i18n` leading to "en.Edit" in show page.
In a PM, if a user has made a post, and is later removed from the PM, they can still edit their own post. This can be done either if they happen to have a composer open in an active tab, or by just manually sending an HTTP request.
The post guardian is missing a basic check, can_see_post_topic? when we determine whether a user can edit a post or not. This basic check is already in place when we determine whether a user can see the post in the first place.
This PR adds in the missing check, so that if the user tries to edit their post after being removed, they'll receive a 403.
It also adds a MessageBus message scoped to the affected user and topic when they are removed from the PM, which will redirect them to their inbox. This helps avoid a stale tab where they are still in the PM which they by right can now no longer see.
This is a small clean-up PR that does the following:
- Convert api-keys.hbs to a RouteTemplate backed api-keys.gjs.
- Move the sub-page templates (index, show, new) into /api-keys sub-directory.
- Removes some styles that aren't used after the admin UI conversion.
In the new admin UI conventions, button labels and section headings should be sentence case, rather than title case.
This PR performs a sweep of all the fragments visible when using the sidebar layout.
This PR introduces a base page object for admin pages. Since we're standardizing using components, this makes writing tests easier by abstracting away details about selectors.
A handful of the page objects for admin pages were placed directly under /page_objects rather than under /page_objects/pages with the others. This PR simply moves them.
We want to avoid surprises when we make the new admin sidebar baseline, so in addition to announcements, we're also adding a problem check that informs you if you don't have it enabled for any group yet.
In #30096 we converted the API keys UI to follow the new admin UI guidelines.
During this conversion, the step where you get a chance to copy the API key after creating, was lost due to a rebase mistake.
This re-introduces it.
When using FK select, we include a "None" option automatically. However, for required select fields, "None" isn't a valid option, so we exclude it instead.
These controller tests are passing locally and in CI, but are failing the build when run in parallel.
I managed to recreate the failures by running the entire suite with turbo_spec and the right seed locally. After these changes, the parallel suite passes locally as well. 🤞
We're changing the default of hide_email_address_taken to true. This is a trade-off we want to make, as it prevents account enumeration with minimal impact on legitimate users. If you forget you have an account and try to sign up again with the same e-mail you'll receive an e-mail letting you know.
We already add the "delete user" and "delete and block user" options to the drop-down for potential spam, but we should do this for potentially illegal posts as well.
This is entirely based on the implementation for the potential spam one, including caching the status on the reviewable record.
Also note that just as for potential spam, the user must be "deletable" for the option to appear.
I also took the liberty to move the options in the drop-down to what I think is a more intuitive place. (Between delete post and suspend/silence user.)
The Admin UI guidelines states that buttons should have text, not icons. This was an oversight on the admin emoji listing.
Part of this change is also opportunistically removing the CSS file for admin emojis, none of which is used any more since the conversion.
We recently tried to default the normalize_emails site setting to true to avoid spam. What this does is it considers e-mails the same regardless of plus addressing, e.g. [email protected] == [email protected]. This caused some problems for SSO users.
This PR makes it so that DiscourseConnect never normalizes e-mails.
Because of an oversight in a previous PR, the breadcrumb link when visiting Admin > Emoji > Settings was broken. The correct path is customize, not config.
The current breadcrumb separators are ">" characters that are added as pseudo-elements. These become part of the clickable area for the links, which causes mis-clicks.
This PR does two things:
- Replace the pseudo-element with a DIcon.
- Make sure the separator is not clickable.
As part of a previous fix we changed which groups are serialized for a user, in order to fix a bug in the default group selector under user preferences.
However, we should only change this when serializing the current user. This change combines the old code-path and the new based on who is serializing.
When performing bulk dismissal in Unread and New views, the dismiss button stays at the top of the UI. Because of this we want to provide the dismiss action also in the "sticky" menu that's always in view, even when scrolling a long list of topics.
I was skimming through existing pages to get a feel for the admin UI guidelines. I noticed that this part was missing its margin. On some further investigation, it seems that a single CSS selector, .award-badge was being used both for the section and for the button in the header, so I decided to 1) separate the two and 2) add in the missing margin.
The normalize_emails setting makes it so that only canonical e-mails are considered for validation purposes. This means disallowing "plus addressing". For example, with this enabled, [email protected] and [email protected] are considered the same address, and you can only sign up with one of them.
Currently this is disabled by default, leading to a lot of spam sign-ups. It's healthier to consider this an opt-out setting.
In #29272 we added a backwards-compatible way to prevent duplicate problem check trackers. However, there was a mistake in the PR that would instead create duplicate admin notices. As a result, a number of admins now have multiple copies of the same admin notice in their dashboard.
The root cause was fixed in #29329, preventing new duplicates.
This PR is here to clean up notices that were already created.
Admin notices are meant to be ephemeral. Instead of going through hoops to delete duplicates and update the remaining notices' unstructured field with the correct target, it is a lot less error-prone to delete all notices and let the problem check system re-create them.
The real-time checks run every time the dashboard is loaded, so they will appear to never have been deleted. Any notices related to scheduled checks will be added back on the next run. This will happen within at most one hour, and isn't time sensitive.
As part of #29272 we made a unique index work on PG13 by introducing a dummy string to represent "NULL".
We missed one spot, leading to a potential for duplicate admin notices for problems without a target.
This fixes that.
We added NULLS NOT DISTINCT to a unique index on problem_check_trackers.
This option is only available in PG15+. It does not in itself break PG13, but restoring a PG15+ backup to PG13 currently errors out. It seems this is an operation that's more common than we first thought.
This commit fixes that by removing the NULLS NOT DISTINCT.
We already have another, backwards-compatible approach to do the same thing in place, so this shouldn't change existing behaviour.
When a user is missing required fields, they are required to fill those up before continuing to interact with the forum. This applies to admins as well.
We keep a whitelist of paths that can still be visited in this mode: FAQ, About, 2FA setup, and any admin route for admins.
We concluded that admins should still be able to enable safe mode even with missing required fields. Since plugins etc. can potentially mess with the ability to fill those up.
We're seeing errors in logs due to some sites setting the reserved_usernames setting to nil. This is causing multiple use cases upstream of User#reserved_username? to error out.
This commit changes from using the raw #reserved_usernames to using the #reserved_usernames_map helper which exists on list-type site settings. It returns an empty array if the raw value is nil or empty string.
When adding or updating a custom user field to apply to all users (retroactively) we want to alert the admin that this will force all existing users to fill up the field before they are able to access the forum again.
However, we currently show this prompt when making changes only to other attributes on the custom field, i.e. the requirement hasn't changed.
This commit fixes that.
In #29169 we added a NULLS NOT DISTINCT option to the unique index on problem_check_trackers. This is to enforce uniqueness even when the target is NULL. (Postgres considers all NULLs to be distinct by default.)
However, this only works in PG15. In PG13 it does nothing.
This commit adds a default dummy string value __NULL__ to target. Since it's a string, PG13 will be able to correctly identify duplicate records.
We're expecting the period param to be something that neatly coerces into a symbol. If we receive something like a nested parameter, this will blow up.
This commit raises an InvalidParameters exception in the case of a non-stringy period parameter.
We're expecting the ID param to be something that neatly coerces into an ID. If we receive something like a nested parameter, this will blow up. (We already handle the case of arrays.)
This commit raises an InvalidParameters exception in the case of a nested ID.
We're expecting the page param to be something that neatly coerces into an integer. If we receive something like a nested parameter, this will blow up. (I'm sure there are other examples as well.)
This commit falls back to a page value of 1 if the coercion fails.
When converting the user custom fields admin form in #29070, I accidentally removed the plugin outlet after-admin-user-fields. This is used by the discourse-authentication-validations plugin, which is now broken on main core.
This commit adds back the plugin outlet in core.
By default, when checking uniqueness on a tuple for the purposes of enforcing a unique index, PostgreSQL considers NULLs to be distinct values. Because of this we could incorrectly have multiple entries with { identifier: "rails_env", target: nil } created due to race conditions. This would then cause errors at runtime.
Dismissing admin notices is an admin-only action. This is enforced on the back-end both by a routing constraint and a policy in the relevant service.
However, we still unconditionally display the "Dismiss" button to anyone with access to the admin dashboard. When clicked, it results in a 404 modal (due to the routing constraint.)
With this change we only render the dismiss button for admins.
If you have the admin dashboard open, and one of the admin notices listed has already been dismissed (e.g. in another tab, or by another admin) we would show an ugly "FAILED" modal.
This change makes the admin dismiss endpoint idempotent. If the admin notice is already destroyed, then respond with 200. This will also correctly remove it from the list in the front-end.
After #28603, the options "agree and suspend" and "agree and silence" in the review queue weren't working. This was happening because the optionalService, when used as a decorator, needs a name argument to work properly. We were also lacking tests for this.
At a point we split FileStore#download into two methods, one which returns nil (#download) and one which raises an exception (#download!) if there's a download error.
When we did this we added a deprecation warning to the #download method.
It has now been over a year and I have checked all relevant internal cases, so I think it's about time we remove this warning.
When running checks, we look to the existing problem check trackers and try to grab their ProblemCheck classes.
In some cases this is no longer in the problem check repository, e.g. when the check was part of a plugin that has been uninstalled.
In the case where the check was scheduled, this would lead to an error in one of the jobs
his is a new feature that lets admins dismiss notices from the dashboard. This helps with self-service in cases where a notice is "stuck", while we work on provisions to prevent "sticking" in the first place.
We're seeing a lot of log noise coming from unhandled exceptions stemming from requests to TopicsController#show where id is passed in as an array.
In the implementation of the method, we assume that if id is present it will be a string. This is because one of the routes to this action uses :id as a URL fragment, and so must be a string. However, there are other routes that go to this endpoint as well. Some of them don't have this URL fragment, so you can pass an arbitrary id query parameter.
Instead of a downstream unhandled exception, we raise a Discourse::InvalidParameters upfront.
To test the restricted routing when filling up required fields, we fill up the field and then navigate to the root path and checking that we're not redirected.
This is somewhat flaky, and the screenshot shows we are back at the profile page, without any prompt to fill up fields.
My hypothesis is in cases where the backend is "slow" to respond, we're navigating away from the page before the request finishes (which will redirect back to the profile page.)
This PR adds an expectation after saving, to wait until the unrestricted profile page is rendered, before navigating away.
This check was checking the wrong scope, causing problems in certain edge conditions, for example:
1. Admin adds an "on signup" field that isn't editable after signup.
2. Admin adds a "for all users" field.
3. User goes and fills up the "for all users" field from 2.
4. User is now stuck on the required fields page without any fields showing.
With this change, we only consider "for all users" fields when asking if required custom fields are filled in.
We need a way to disable certain checks programatically, e.g. on Discourse hosting. This PR adds a configuration option for this, and makes it so that disabled checks aren't run as part of #run_all.
We changed the design of the member access wizard step to use toggle groups instead of switches. To support existing designs for notices, we need another plugin outlet.
We support a low-level construct called "inline checks", which you can use to register a problem ad-hoc from within application code.
Problems registered by inline checks never show up in the admin dashboard, this is because when loading the dashboard, we run all realtime checks and look for problems. Because of an oversight, we considered inline checks to be "realtime", causing them to be run and clear their problem status.
To fix this, we don't consider inline checks to be realtime, to prevent them from running when loading the admin dashboard.
When creating a shared draft, we're recording topic view stats on the draft and then pass those on when the draft is published, conflating the actual view count.
This fixes that by not registering topic views if the topic is a shared draft.
If a user has a required action, e.g. adding a 2FA method or filling in new required fields, we disable client-side routing except to allowed pages.
This led to a situation where a user might navigate away from e.g. the profile page to look at the new ToS, and then being "stuck" due to not knowing how to get back to accept the new terms.
This PR makes it so that if you click any restricted link, instead of doing nothing we transition the user back to the page where they can take the required action.
The OutOfDateThemes problem check is using an old method of setting the message, by overriding #message. It should instead use #translation_keys. (By chance I noticed the same thing applies to UnreachableThemes.
### What is the problem?
We have recently added a new option to add user fields required for existing users. This is in contrast to requiring fields only on sign-up.
This revealed an existing problem. Consider the following:
1. User A signs up.
2. Admin adds a new user field required on sign-up. (Should not apply to User A since they already signed up.)
3. User A tries to update their profile.
**Expected behaviour:**
No problem.
**Actual behaviour:**
User A receives an error saying they didn't fill up all required fields.
### How does this fix it?
When updating profile, we only check that required fields that are "for all users" are filled. Additionally, we check that fields that were required on sign-up and have previously been filled are not blanked out.
On the review page, once you select a category to filter by, while you can still change the category, you can not clear it.
After this commit, we pass the "clearable" select-kit option through.
When a user action is required and enforced, such as filling up newly added required fields or adding a 2FA method, we disable routing on the client-side. However, this could be bypassed by first loading an always allowed page, such as /faq and then client-side routing away from there.
This commit fixes that by 1) moving the logic for checking if routing is restricted and if a given path is allowed into a service and 2) hoisting the willTransition hook into the application router and use the newly created service to check whether to abort transitions or not.
Badges can have their associated image uploads deleted. When this happens, any user who has that badge will have their profile page error out.
After this fix, when deleting an upload that's associated with a badge, we nullify the foreign key ID on the badge. This makes the existing safeguard work correctly.
Often when the pre-commit hook is run and reports a problem, I want to follow up by running the auto-correct command of the respective linter, but that means I need to remember what to run. "Let's see. Did prettier need a double-dash before write or not?"
This change adds a lefthook run fix-staged command that runs linter auto-corrects on any staged files.
We want to allow admins to make new required fields apply to existing users. In order for this to work we need to have a way to make those users fill up the fields on their next page load. This is very similar to how adding a 2FA requirement post-fact works. Users will be redirected to a page where they can fill up the remaining required fields, and until they do that they won't be able to do anything else.
We previously migrated field_type from a string to an integer backed enum. Part of this involved renaming a column in a post migration, swapping out field_type:string for field_type:integer. This borks the ActiveRecord cache since the application is already running. Rebooting fixes it, but we want to avoid having this happen in the first place.
We recently fixed a problem where secure upload images weren't re-attached when sending the activity summary e-mail.
This fix contained a bug that would lead to n copies of the e-mail body being included, n being the number of duplicates. This is because #fix_parts_after_attachments! was called once per attachment, and adding more parts to the multipart e-mail.
This PR fixes that by:
Adding a failing test case for the above.
Moving the looping over multiple posts into #fix_parts_after_attachments! itself.
The test that checks that securely uploaded images are re-attached to the digest e-mail wasn't rendering the actual digest e-mail template. This change fixes that.
Some tooling may rely on an unsafe-none cross origin opener policy to work. This change adds a hidden site setting that can be used to list referrers where we add this header instead of the default one configured in cross_origin_opener_policy_header.
In #26642 we introduced a change that re-attaches securely uploaded images in the digest e-mail. However, this change assumed that the type argument to the Email::Sender constructor would be a symbol, but when it is coming from the UserEmail job it is a string. This PR fixes that.
Follow up to: #27444. In that PR we added a new integer column for UserField#field_type and populated the data based on the old text field.
In this PR we drop the old text column and swap in the new integer (enum) column.
Currently this column is a text column, but by right should only take on one of the values text, confirm, dropdown, multiselect. We can convert this to an ActiveRecord enum instead.
This PR adds a new integer column (field_type_enum) and populates it based on the existing text column (field_type) and adds an alias to replace the latter with the former.
The UsersController#modify_user_params method is deprecated and replaced with a plugin modifier (users_controller_update_user_params). It is marked for removal in 3.2. This PR removes it.
We're planning to implement a feature that allows adding required fields for existing users. This PR does some preparatory refactoring to make that possible. There should be no changes to existing behaviour. Just a small update to the admin UI.
This PR introduces a basic AdminNotice model to store these notices. Admin notices are categorized by their source/type (currently only notices from problem check.) They also have a priority.
AuthProvider#enabled_setting=, used primarily by plugins, has been deprecated since version 2.9, in favour of Authenticator#enabled?. This PR confirms we are seeing no more usage and removes the method.
In #22851 we added a dependent strategy for deleting upload references when a draft is destroyed. This, however, didn't catch all cases, because we still have some code that issues DELETE drafts queries directly to the database. Specifically in the weekly cleanup job handled by Draft#cleanup!.
This PR fixes that by turning the raw query into an ActiveRecord #destroy_all, which will invoke the dependent strategy that ultimately deletes the upload references. It also includes a post migration to clear orphaned upload references that are already in the database.
LinkedIn has grandfathered its old OAuth2 provider. This can only be used by existing apps. New apps have to use the new OIDC provider.
This PR adds a linkedin_oidc provider to core. This will exist alongside the discourse-linkedin-auth plugin, which will be kept for those still using the deprecated provider.