diff --git a/.github/workflows/prod-build-deploy-azure.yml b/.github/workflows/azure-prod-build-deploy.yml similarity index 98% rename from .github/workflows/prod-build-deploy-azure.yml rename to .github/workflows/azure-prod-build-deploy.yml index 14cd2057aa..2a10b09863 100644 --- a/.github/workflows/prod-build-deploy-azure.yml +++ b/.github/workflows/azure-prod-build-deploy.yml @@ -1,4 +1,4 @@ -name: Production (Azure) - Build and Deploy +name: Azure Production - Build and Deploy # **What it does**: Builds and deploys the default branch to production # **Why we have it**: To enable us to deploy the latest to production whenever necessary rather than relying on PR merges. @@ -21,10 +21,10 @@ concurrency: cancel-in-progress: false jobs: - build-and-deploy-prod-azure: + azure-prod-build-and-deploy: if: ${{ github.repository == 'github/docs-internal' }} runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 20 environment: name: production url: 'https://docs.github.com' diff --git a/assets/images/help/business-accounts/remove-admin.png b/assets/images/help/business-accounts/remove-admin.png index 75bf4ab069..dd06527a29 100644 Binary files a/assets/images/help/business-accounts/remove-admin.png and b/assets/images/help/business-accounts/remove-admin.png differ diff --git a/assets/images/help/repository/add-tag-protection-rule.png b/assets/images/help/repository/add-tag-protection-rule.png new file mode 100644 index 0000000000..5b3d4c8603 Binary files /dev/null and b/assets/images/help/repository/add-tag-protection-rule.png differ diff --git a/assets/images/help/repository/dependabot-alerts-closed.png b/assets/images/help/repository/dependabot-alerts-closed.png new file mode 100644 index 0000000000..f5687ae93f Binary files /dev/null and b/assets/images/help/repository/dependabot-alerts-closed.png differ diff --git a/assets/images/help/repository/dependabot-alerts-select-closed-alert.png b/assets/images/help/repository/dependabot-alerts-select-closed-alert.png new file mode 100644 index 0000000000..33f9c3364f Binary files /dev/null and b/assets/images/help/repository/dependabot-alerts-select-closed-alert.png differ diff --git a/assets/images/help/repository/new-tag-protection-rule.png b/assets/images/help/repository/new-tag-protection-rule.png new file mode 100644 index 0000000000..7df6de9590 Binary files /dev/null and b/assets/images/help/repository/new-tag-protection-rule.png differ diff --git a/assets/images/help/repository/reopen-dismissed-alert.png b/assets/images/help/repository/reopen-dismissed-alert.png new file mode 100644 index 0000000000..07a1cd9495 Binary files /dev/null and b/assets/images/help/repository/reopen-dismissed-alert.png differ diff --git a/assets/images/help/repository/set-tag-protection-pattern.png b/assets/images/help/repository/set-tag-protection-pattern.png new file mode 100644 index 0000000000..7118b4058a Binary files /dev/null and b/assets/images/help/repository/set-tag-protection-pattern.png differ diff --git a/components/page-header/LanguagePicker.tsx b/components/page-header/LanguagePicker.tsx index f4ac20ef6f..a98362257b 100644 --- a/components/page-header/LanguagePicker.tsx +++ b/components/page-header/LanguagePicker.tsx @@ -1,9 +1,14 @@ import { useRouter } from 'next/router' +import Cookies from 'js-cookie' + import { Link } from 'components/Link' import { useLanguages } from 'components/context/LanguagesContext' import { Picker } from 'components/ui/Picker' import { useTranslation } from 'components/hooks/useTranslation' +// This value is replicated in two places! See middleware/detect-language.js +const PREFERRED_LOCALE_COOKIE_NAME = 'preferredlang' + type Props = { variant?: 'inline' } @@ -22,6 +27,22 @@ export const LanguagePicker = ({ variant }: Props) => { // in a "denormalized" way. const routerPath = router.asPath.split('#')[0] + function rememberPreferredLanguage(code: string) { + try { + Cookies.set(PREFERRED_LOCALE_COOKIE_NAME, code, { + expires: 365, + secure: document.location.protocol !== 'http:', + }) + } catch (err) { + // You can never be too careful because setting a cookie + // can fail. For example, some browser + // extensions disallow all setting of cookies and attempts + // at the `document.cookie` setter could throw. Just swallow + // and move on. + console.warn('Unable to set preferred language cookie', err) + } + } + return ( { text: lang.nativeName || lang.name, selected: lang === selectedLang, item: ( - + { + rememberPreferredLanguage(lang.code) + }} + > {lang.nativeName ? ( <> {lang.nativeName} ( diff --git a/content/admin/configuration/configuring-github-connect/managing-github-connect.md b/content/admin/configuration/configuring-github-connect/managing-github-connect.md index c81bf7b862..6989f4d264 100644 --- a/content/admin/configuration/configuring-github-connect/managing-github-connect.md +++ b/content/admin/configuration/configuring-github-connect/managing-github-connect.md @@ -41,6 +41,8 @@ When you enable {% data variables.product.prodname_github_connect %}, you config To use {% data variables.product.prodname_github_connect %}, you must have an organization or enterprise account on {% data variables.product.prodname_dotcom_the_website %} that uses {% data variables.product.prodname_ghe_cloud %}. You may already have {% data variables.product.prodname_ghe_cloud %} included in your plan. {% data reusables.enterprise.link-to-ghec-trial %} {% ifversion ghes %} +If your organization or enterprise account on {% data variables.product.prodname_dotcom_the_website %} uses IP allow lists, you must add the IP address or network for {% data variables.product.product_location %} to your IP allow list on {% data variables.product.prodname_dotcom_the_website %}. For more information, see "[Managing allowed IP addresses for your organization](/enterprise-cloud@latest/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization)" and "[Enforcing policies for security settings in your enterprise](/enterprise-cloud@latest/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise#managing-allowed-ip-addresses-for-organizations-in-your-enterprise)" in the {% data variables.product.prodname_ghe_cloud %} documentation. + To configure a connection, your proxy configuration must allow connectivity to `github.com`, `api.github.com`, and `uploads.github.com`. For more information, see "[Configuring an outbound web proxy server](/enterprise/{{ currentVersion }}/admin/guides/installation/configuring-an-outbound-web-proxy-server)." {% endif %} diff --git a/content/admin/identity-and-access-management/managing-iam-for-your-enterprise/configuring-saml-single-sign-on-for-your-enterprise.md b/content/admin/identity-and-access-management/managing-iam-for-your-enterprise/configuring-saml-single-sign-on-for-your-enterprise.md index 2eecbac189..33ea8e4054 100644 --- a/content/admin/identity-and-access-management/managing-iam-for-your-enterprise/configuring-saml-single-sign-on-for-your-enterprise.md +++ b/content/admin/identity-and-access-management/managing-iam-for-your-enterprise/configuring-saml-single-sign-on-for-your-enterprise.md @@ -33,8 +33,6 @@ redirect_from: {% data reusables.saml.about-saml-access-enterprise-account %} For more information, see "[Viewing and managing a user's SAML access to your enterprise account](/admin/user-management/managing-users-in-your-enterprise/viewing-and-managing-a-users-saml-access-to-your-enterprise)." -{% data reusables.saml.cannot-update-existing-saml-settings %} - {% data reusables.saml.saml-disabled-linked-identities-removed %} {% data reusables.scim.enterprise-account-scim %} diff --git a/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users.md b/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users.md index 3c4a7db830..15f8466772 100644 --- a/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users.md +++ b/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users.md @@ -25,7 +25,11 @@ With {% data variables.product.prodname_emus %}, your enterprise uses SAML SSO t After you configure SAML SSO, we recommend storing your recovery codes so you can recover access to your enterprise in the event that your identity provider is unavailable. -{% data reusables.saml.cannot-update-existing-saml-settings %} +{% note %} + +**Note:** When SAML SSO is enabled, the only setting you can update on {% data variables.product.prodname_dotcom %} for your existing SAML configuration is the SAML certificate. If you need to update the Sign on URL or Issuer, you must first disable SAML SSO and then reconfigure SAML SSO with the new settings. + +{% endnote %} ## Configuring SAML single sign-on for {% data variables.product.prodname_emus %} diff --git a/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users-with-okta.md b/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users-with-okta.md index 4297458ce2..2ac54e96d3 100644 --- a/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users-with-okta.md +++ b/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users-with-okta.md @@ -80,6 +80,8 @@ To configure provisioning, the setup user with the **@SHORT-CODE_admin* After you have configured SAML SSO and provisioning, you will be able provision new users on {% data variables.product.prodname_dotcom_the_website %} by assigning users to the {% data variables.product.prodname_emu_idp_application %} application. +{% data reusables.scim.emu-scim-rate-limit %} + You can also automatically manage organization membership by assigning groups to the application and adding them to the "Push Groups" tab in Okta. When the group is provisioned successfully, it will be available to connect to teams in the enterprise's organizations. For more information about managing teams, see "[Managing team memberships with identity provider groups](/github/setting-up-and-managing-your-enterprise/managing-your-enterprise-users-with-your-identity-provider/managing-team-memberships-with-identity-provider-groups)." When assigning users, you can use the "Roles" attribute in the {% data variables.product.prodname_emu_idp_application %} application to set a user's role in your enterprise on {% data variables.product.product_name %}. For more information on roles, see "[Roles in an enterprise](/github/setting-up-and-managing-your-enterprise/managing-users-in-your-enterprise/roles-in-an-enterprise)." diff --git a/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users.md b/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users.md index 6b785892bf..e64839136e 100644 --- a/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users.md +++ b/content/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/configuring-scim-provisioning-for-enterprise-managed-users.md @@ -56,6 +56,8 @@ To configure provisioning for your {% data variables.product.prodname_emu_enterp After creating your personal access token and storing it securely, you can configure provisioning on your identity provider. +{% data reusables.scim.emu-scim-rate-limit %} + To configure Azure Active Directory to provision users for your {% data variables.product.prodname_emu_enterprise %}, see [Tutorial: Configure GitHub Enterprise Managed User for automatic user provisioning](https://docs.microsoft.com/en-us/azure/active-directory/saas-apps/github-enterprise-managed-user-provisioning-tutorial) in the Azure AD documentation. To configure Okta to provision users for your {% data variables.product.prodname_emu_enterprise %}, see "[Configuring SCIM provisioning for Enterprise Managed Users with Okta](/github/setting-up-and-managing-your-enterprise/managing-your-enterprise-users-with-your-identity-provider/configuring-scim-provisioning-for-enterprise-managed-users-with-okta)." diff --git a/content/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise.md b/content/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise.md index 69f67199d5..dabe75e515 100644 --- a/content/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise.md +++ b/content/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise.md @@ -69,7 +69,7 @@ You can restrict network traffic to your enterprise on {% data variables.product {% elsif ghec %} -Enterprise owners can restrict access to assets owned by organizations in an enterprise by configuring an allow list for specific IP addresses. {% data reusables.identity-and-permissions.ip-allow-lists-example-and-restrictions %} +Enterprise owners can restrict access to private assets owned by organizations in an enterprise by configuring an allow list for specific IP addresses. {% data reusables.identity-and-permissions.ip-allow-lists-example-and-restrictions %} {% data reusables.identity-and-permissions.ip-allow-lists-cidr-notation %} diff --git a/content/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise.md b/content/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise.md index 250b63d8bd..f4a4b50aab 100644 --- a/content/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise.md +++ b/content/admin/user-management/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise.md @@ -47,8 +47,7 @@ If your enterprise uses {% data variables.product.prodname_emus %}, enterprise o {% data reusables.enterprise-accounts.access-enterprise %} {% data reusables.enterprise-accounts.people-tab %} -1. In the left sidebar, click **Administrators**. - ![Administrators tab in the left sidebar](/assets/images/help/business-accounts/administrators-tab.png) +{% data reusables.enterprise-accounts.administrators-tab %} 1. Above the list of administrators, click {% ifversion ghec %}**Invite admin**{% elsif ghes %}**Add owner**{% endif %}. {% ifversion ghec %} !["Invite admin" button above the list of enterprise owners](/assets/images/help/business-accounts/invite-admin-button.png) @@ -70,10 +69,11 @@ Only enterprise owners can remove other enterprise administrators from the enter {% data reusables.enterprise-accounts.access-enterprise %} {% data reusables.enterprise-accounts.people-tab %} -1. Next to the username of the person you'd like to remove, click {% octicon "gear" aria-label="The Settings gear" %}, then click **Remove owner**{% ifversion ghec %} or **Remove billing manager**{% endif %}. +{% data reusables.enterprise-accounts.administrators-tab %} +1. Next to the username of the person you'd like to remove, click {% octicon "gear" aria-label="The Settings gear" %}, then click {% ifversion ghes %}**Remove owner**{% elsif ghec %}**Convert to member**{% endif %}. {% ifversion ghec %} ![Settings gear with menu option to remove an enterprise administrator](/assets/images/help/business-accounts/remove-admin.png) {% elsif ghes %} ![Settings gear with menu option to remove an enterprise administrator](/assets/images/help/business-accounts/ghes-remove-owner.png) {% endif %} -1. Read the confirmation, then click **Remove owner**{% ifversion ghec %} or **Remove billing manager**{% endif %}. +1. Read the confirmation, then click {% ifversion ghes %}**Remove owner**{% elsif ghec %}**Yes, convert USERNAME to member**{% endif %}. diff --git a/content/billing/managing-billing-for-your-github-account/downgrading-your-github-subscription.md b/content/billing/managing-billing-for-your-github-account/downgrading-your-github-subscription.md index b7d539616b..4223600d96 100644 --- a/content/billing/managing-billing-for-your-github-account/downgrading-your-github-subscription.md +++ b/content/billing/managing-billing-for-your-github-account/downgrading-your-github-subscription.md @@ -53,6 +53,13 @@ If you downgrade your organization from {% data variables.product.prodname_team If you downgrade your organization from {% data variables.product.prodname_ghe_cloud %} to {% data variables.product.prodname_team %} or {% data variables.product.prodname_free_team %}, the account will lose access to advanced security, compliance, and deployment controls. {% data reusables.gated-features.more-info %} + +{% note %} + +**Note:** If you're currently trialing {% data variables.product.prodname_ghe_cloud %}, and you don't purchase {% data variables.product.prodname_enterprise %} before the trial ends, your organization will be automatically downgraded to {% data variables.product.prodname_free_team %} or {% data variables.product.prodname_team %}. For more information, see "[Setting up a trial of {% data variables.product.prodname_ghe_cloud %}](/get-started/signing-up-for-github/setting-up-a-trial-of-github-enterprise-cloud#finishing-your-trial)." + +{% endnote %} + {% data reusables.organizations.billing-settings %} 1. Under "Current plan", use the **Edit** drop-down and click the downgrade option you want. ![Downgrade button](/assets/images/help/billing/downgrade-option-button.png) diff --git a/content/billing/managing-licenses-for-visual-studio-subscriptions-with-github-enterprise/setting-up-visual-studio-subscriptions-with-github-enterprise.md b/content/billing/managing-licenses-for-visual-studio-subscriptions-with-github-enterprise/setting-up-visual-studio-subscriptions-with-github-enterprise.md index 6d51a952ce..cbd795eb3a 100644 --- a/content/billing/managing-licenses-for-visual-studio-subscriptions-with-github-enterprise/setting-up-visual-studio-subscriptions-with-github-enterprise.md +++ b/content/billing/managing-licenses-for-visual-studio-subscriptions-with-github-enterprise/setting-up-visual-studio-subscriptions-with-github-enterprise.md @@ -16,6 +16,9 @@ shortTitle: Set up This guide shows you how your team can get {% data variables.product.prodname_vs %} subscribers licensed and started with {% data variables.product.prodname_enterprise %}. +If you prefer video, you can watch [Setting up your {% data variables.product.prodname_enterprise %} licenses with {% data variables.product.prodname_vs %} subscriptions](https://www.youtube.com/watch?v=P_zBgp_BE_I) on +Microsoft Visual Studio's YouTube channel. + ## Roles for {% data variables.product.prodname_vss_ghe %} Before setting up {% data variables.product.prodname_vss_ghe %}, it's important to understand the roles for this combined offering. diff --git a/content/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning.md b/content/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning.md index 88765f488b..1c40c8ccba 100644 --- a/content/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning.md +++ b/content/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning.md @@ -105,7 +105,7 @@ Any valid SARIF 2.1.0 output file can be uploaded, however, {% data variables.pr | `properties.tags[]` | **Optional.** An array of strings. {% data variables.product.prodname_code_scanning_capc %} uses `tags` to allow you to filter results on {% data variables.product.prodname_dotcom %}. For example, it is possible to filter to all results that have the tag `security`. | `properties.precision` | **Recommended.** A string that indicates how often the results indicated by this rule are true. For example, if a rule has a known high false-positive rate, the precision should be `low`. {% data variables.product.prodname_code_scanning_capc %} orders results by precision on {% data variables.product.prodname_dotcom %} so that the results with the highest `level`, and highest `precision` are shown first. Can be one of: `very-high`, `high`, `medium`, or `low`. {% ifversion fpt or ghes > 3.1 or ghae or ghec %} | `properties.problem.severity` | **Recommended.** A string that indicates the level of severity of any alerts generated by a non-security query. This, with the `properties.precision` property, determines whether the results are displayed by default on {% data variables.product.prodname_dotcom %} so that the results with the highest `problem.severity`, and highest `precision` are shown first. Can be one of: `error`, `warning`, or `recommendation`. -| `properties.security-severity` | **Recommended.** A score that indicates the level of severity, between 0.0 and 10.0, for security queries (`@tags` includes `security`). This, with the `properties.precision` property, determines whether the results are displayed by default on {% data variables.product.prodname_dotcom %} so that the results with the highest `security-severity`, and highest `precision` are shown first. {% data variables.product.prodname_code_scanning_capc %} translates numerical scores as follows: over 9.0 is `critical`, 7.0 to 8.9 is `high`, 4.0 to 6.9 is `medium` and 3.9 or less is `low`. {% endif %} +| `properties.security-severity` | **Recommended.** A string representing a score that indicates the level of severity, between 0.0 and 10.0, for security queries (`@tags` includes `security`). This, with the `properties.precision` property, determines whether the results are displayed by default on {% data variables.product.prodname_dotcom %} so that the results with the highest `security-severity`, and highest `precision` are shown first. {% data variables.product.prodname_code_scanning_capc %} translates numerical scores as follows: over 9.0 is `critical`, 7.0 to 8.9 is `high`, 4.0 to 6.9 is `medium` and 3.9 or less is `low`. {% endif %} ### `result` object @@ -114,7 +114,7 @@ Any valid SARIF 2.1.0 output file can be uploaded, however, {% data variables.pr | Name | Description | |----|----| | `ruleId`| **Optional.** The unique identifier of the rule (`reportingDescriptor.id`). For more information, see the [`reportingDescriptor` object](#reportingdescriptor-object). {% data variables.product.prodname_code_scanning_capc %} uses the rule identifier to filter results by rule on {% data variables.product.prodname_dotcom %}. -| `ruleIndex`| **Optional.** The index of the associated rule (`reportingDescriptor` object) in the tool component `rules` array. For more information, see the [`run` object](#run-object). +| `ruleIndex`| **Optional.** The index of the associated rule (`reportingDescriptor` object) in the tool component `rules` array. For more information, see the [`run` object](#run-object). The allowed range for this property 0 to 2^63 - 1. | `rule`| **Optional.** A reference used to locate the rule (reporting descriptor) for this result. For more information, see the [`reportingDescriptor` object](#reportingdescriptor-object). | `level`| **Optional.** The severity of the result. This level overrides the default severity defined by the rule. {% data variables.product.prodname_code_scanning_capc %} uses the level to filter results by severity on {% data variables.product.prodname_dotcom %}. | `message.text`| **Required.** A message that describes the result. {% data variables.product.prodname_code_scanning_capc %} displays the message text as the title of the result. Only the first sentence of the message will be displayed when visible space is limited. @@ -129,7 +129,7 @@ A location within a programming artifact, such as a file in the repository or a | Name | Description | |----|----| -| `location.id` | **Optional.** A unique identifier that distinguishes this location from all other locations within a single result object. +| `location.id` | **Optional.** A unique identifier that distinguishes this location from all other locations within a single result object. The allowed range for this property 0 to 2^63 - 1. | `location.physicalLocation` | **Required.** Identifies the artifact and region. For more information, see the [`physicalLocation`](#physicallocation-object). | `location.message.text` | **Optional.** A message relevant to the location. diff --git a/content/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/viewing-and-updating-vulnerable-dependencies-in-your-repository.md b/content/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/viewing-and-updating-vulnerable-dependencies-in-your-repository.md index 4029faa7f6..ce51732a37 100644 --- a/content/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/viewing-and-updating-vulnerable-dependencies-in-your-repository.md +++ b/content/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/viewing-and-updating-vulnerable-dependencies-in-your-repository.md @@ -57,7 +57,7 @@ Each {% data variables.product.prodname_dependabot %} alert has a unique numeric 1. Optionally, if there isn't already a {% data variables.product.prodname_dependabot_security_updates %} update for the alert, to create a pull request to resolve the vulnerability, click **Create {% data variables.product.prodname_dependabot %} security update**. ![Create {% data variables.product.prodname_dependabot %} security update button](/assets/images/help/repository/create-dependabot-security-update-button-ungrouped.png) 1. When you're ready to update your dependency and resolve the vulnerability, merge the pull request. Each pull request raised by {% data variables.product.prodname_dependabot %} includes information on commands you can use to control {% data variables.product.prodname_dependabot %}. For more information, see "[Managing pull requests for dependency updates](/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/managing-pull-requests-for-dependency-updates#managing-dependabot-pull-requests-with-comment-commands)." -1. Optionally, if the alert is being fixed, if it's incorrect, or located in unused code, select the "Dismiss" drop-down, and click a reason for dismissing the alert. +1. Optionally, if the alert is being fixed, if it's incorrect, or located in unused code, select the "Dismiss" dropdown, and click a reason for dismissing the alert.{% if reopen-dependabot-alerts %} Unfixed dismissed alerts can be reopened later.{% endif %} ![Choosing reason for dismissing the alert via the "Dismiss" drop-down](/assets/images/help/repository/dependabot-alert-dismiss-drop-down-ungrouped.png) {% elsif ghes = 3.3 %} @@ -94,6 +94,22 @@ Each {% data variables.product.prodname_dependabot %} alert has a unique numeric ![Dismiss security banner](/assets/images/enterprise/3.0/dependabot-alert-dismiss.png) {% endif %} +{% if reopen-dependabot-alerts %} + +## Viewing and updating closed alerts + +{% data reusables.repositories.navigate-to-repo %} +{% data reusables.repositories.sidebar-security %} +{% data reusables.repositories.sidebar-dependabot-alerts %} +1. To just view closed alerts, click **Closed**. + ![Screenshot showing the "Closed" option](/assets/images/help/repository/dependabot-alerts-closed.png) +1. Click the alert that you would like to view or update. + ![Screenshot showing a highlighted dependabot alert](/assets/images/help/repository/dependabot-alerts-select-closed-alert.png) +2. Optionally, if the alert was dismissed and you wish to reopen it, click **Reopen**. + ![Screenshot showing the "Reopen" button](/assets/images/help/repository/reopen-dismissed-alert.png) + +{% endif %} + ## Further reading - "[About alerts for vulnerable dependencies](/code-security/supply-chain-security/about-alerts-for-vulnerable-dependencies)"{% ifversion fpt or ghec or ghes > 3.2 %} diff --git a/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization.md b/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization.md index 2821f7764a..aceb8f9d7a 100644 --- a/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization.md +++ b/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization.md @@ -1,6 +1,6 @@ --- title: Managing allowed IP addresses for your organization -intro: You can restrict access to your organization's assets by configuring a list of IP addresses that are allowed to connect. +intro: You can restrict access to your organization's private assets by configuring a list of IP addresses that are allowed to connect. product: '{% data reusables.gated-features.allowed-ip-addresses %}' redirect_from: - /github/setting-up-and-managing-organizations-and-teams/managing-allowed-ip-addresses-for-your-organization @@ -19,7 +19,7 @@ Organization owners can manage allowed IP addresses for an organization. ## About allowed IP addresses -You can restrict access to organization assets by configuring an allow list for specific IP addresses. {% data reusables.identity-and-permissions.ip-allow-lists-example-and-restrictions %} +You can restrict access to private organization assets by configuring an allow list for specific IP addresses. {% data reusables.identity-and-permissions.ip-allow-lists-example-and-restrictions %} {% data reusables.identity-and-permissions.ip-allow-lists-cidr-notation %} @@ -27,7 +27,7 @@ You can restrict access to organization assets by configuring an allow list for If you set up an allow list you can also choose to automatically add to your allow list any IP addresses configured for {% data variables.product.prodname_github_apps %} that you install in your organization. The creator of a {% data variables.product.prodname_github_app %} can configure an allow list for their application, specifying the IP addresses at which the application runs. By inheriting their allow list into yours, you avoid connection requests from the application being refused. For more information, see "[Allowing access by {% data variables.product.prodname_github_apps %}](#allowing-access-by-github-apps)." -You can also configure allowed IP addresses for the organizations in an enterprise account. For more information, see "[Enforcing policies for security settings in your enterprise](/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise)." +You can also configure allowed IP addresses for the organizations in an enterprise account. For more information, see "[Enforcing policies for security settings in your enterprise](/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise#managing-allowed-ip-addresses-for-organizations-in-your-enterprise)." ## Adding an allowed IP address diff --git a/content/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization.md b/content/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization.md index 0c7d52b579..1e53639563 100644 --- a/content/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization.md +++ b/content/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization.md @@ -109,11 +109,14 @@ Some of the features listed below are limited to organizations using {% data var | Enable project boards | | | | **X** | **X** | | Configure [pull request merges](/articles/configuring-pull-request-merges) | | | | **X** | **X** | | Configure [a publishing source for {% data variables.product.prodname_pages %}](/articles/configuring-a-publishing-source-for-github-pages) | | | | **X** | **X** | +| [Manage branch protection rules](/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule) | | | | | **X** | | [Push to protected branches](/articles/about-protected-branches) | | | | **X** | **X** | +| Merge pull requests on protected branches, even if there are no approving reviews | | | | | **X** |{% ifversion fpt or ghes > 3.4 or ghae-issue-6337 or ghec %} +| Create tags that match a [tag protection rule](/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules) | | | | **X** | **X** | +| Delete tags that match a [tag protection rule](/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules) | | | | | **X** |{% endif %} | [Create and edit repository social cards](/articles/customizing-your-repositorys-social-media-preview) | | | | **X** | **X** |{% ifversion fpt or ghec %} | Limit [interactions in a repository](/communities/moderating-comments-and-conversations/limiting-interactions-in-your-repository)| | | | **X** | **X** |{% endif %} | Delete an issue (see "[Deleting an issue](/articles/deleting-an-issue)") | | | | | **X** | -| Merge pull requests on protected branches, even if there are no approving reviews | | | | | **X** | | [Define code owners for a repository](/articles/about-code-owners) | | | | | **X** | | Add a repository to a team (see "[Managing team access to an organization repository](/organizations/managing-access-to-your-organizations-repositories/managing-team-access-to-an-organization-repository#giving-a-team-access-to-a-repository)" for details) | | | | | **X** | | [Manage outside collaborator access to a repository](/articles/adding-outside-collaborators-to-repositories-in-your-organization) | | | | | **X** | diff --git a/content/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization.md b/content/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization.md index 4fcd5f4736..522f701391 100644 --- a/content/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization.md +++ b/content/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization.md @@ -84,6 +84,8 @@ You can only choose an additional permission if it's not already included in the - **Set interaction limits**: Temporarily restrict certain users from commenting, opening issues, or creating pull requests in your public repository to enforce a period of limited activity. For more information, see "[Limiting interactions in your repository](/communities/moderating-comments-and-conversations/limiting-interactions-in-your-repository)." - **Set the social preview**: Add an identifying image to your repository that appears on social media platforms when your repository is linked. For more information, see "[Customizing your repository's social media preview](/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/customizing-your-repositorys-social-media-preview)." - **Push commits to protected branches**: Push to a branch that is marked as a protected branch. +- **Create protected tags**: Create tags that match a tag protection rule. For more information, see "[Configuring tag protection rules](/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules)." +- **Delete protected tags**: Delete tags that match a tag protection rule. For more information, see "[Configuring tag protection rules](/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules)." ### Security diff --git a/content/organizations/managing-saml-single-sign-on-for-your-organization/enabling-and-testing-saml-single-sign-on-for-your-organization.md b/content/organizations/managing-saml-single-sign-on-for-your-organization/enabling-and-testing-saml-single-sign-on-for-your-organization.md index 4c23e7614b..5ba478b6a8 100644 --- a/content/organizations/managing-saml-single-sign-on-for-your-organization/enabling-and-testing-saml-single-sign-on-for-your-organization.md +++ b/content/organizations/managing-saml-single-sign-on-for-your-organization/enabling-and-testing-saml-single-sign-on-for-your-organization.md @@ -22,8 +22,6 @@ If you enable but don't enforce SAML SSO, organization members who choose not to {% data reusables.saml.saml-disabled-linked-identities-removed %} -{% data reusables.saml.cannot-update-existing-saml-settings %} - ## Enabling and testing SAML single sign-on for your organization Before your enforce SAML SSO in your organization, ensure that you've prepared the organization. For more information, see "[Preparing to enforce SAML single sign-on in your organization](/articles/preparing-to-enforce-saml-single-sign-on-in-your-organization)." diff --git a/content/packages/index.md b/content/packages/index.md index b46d3642e9..61174d989b 100644 --- a/content/packages/index.md +++ b/content/packages/index.md @@ -12,11 +12,11 @@ featuredLinks: - /packages/learn-github-packages/installing-a-package popular: - /packages/working-with-a-github-packages-registry/working-with-the-npm-registry - - '{% ifversion fpt %}/packages/working-with-a-github-packages-registry/working-with-the-container-registry{% else %}/packages/working-with-a-github-packages-registry/working-with-the-docker-registry{% endif %}' + - '{% ifversion fpt or ghec%}/packages/working-with-a-github-packages-registry/working-with-the-container-registry{% else %}/packages/working-with-a-github-packages-registry/working-with-the-docker-registry{% endif %}' - /packages/learn-github-packages - /packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry guideCards: - - '{% ifversion fpt %}/packages/working-with-a-github-packages-registry/working-with-the-container-registry{% else %}/packages/working-with-a-github-packages-registry/working-with-the-docker-registry{% endif %}' + - '{% ifversion fpt or ghec %}/packages/working-with-a-github-packages-registry/working-with-the-container-registry{% else %}/packages/working-with-a-github-packages-registry/working-with-the-docker-registry{% endif %}' - /packages/working-with-a-github-packages-registry/working-with-the-rubygems-registry changelog: label: packages diff --git a/content/packages/working-with-a-github-packages-registry/working-with-the-docker-registry.md b/content/packages/working-with-a-github-packages-registry/working-with-the-docker-registry.md index b7650ff4b4..dc433c384c 100644 --- a/content/packages/working-with-a-github-packages-registry/working-with-the-docker-registry.md +++ b/content/packages/working-with-a-github-packages-registry/working-with-the-docker-registry.md @@ -1,6 +1,6 @@ --- title: Working with the Docker registry -intro: '{% ifversion fpt or ghec %}The Docker registry has now been replaced by the {% data variables.product.prodname_container_registry %}.{% else %}You can push and pull your Docker images using the {% data variables.product.prodname_registry %} Docker registry, which uses the package namespace `https://docker.pkg.github.com`.{% endif %}' +intro: '{% ifversion fpt or ghec %}The Docker registry has now been replaced by the {% data variables.product.prodname_container_registry %}.{% else %}You can push and pull your Docker images using the {% data variables.product.prodname_registry %} Docker registry.{% endif %}' product: '{% data reusables.gated-features.packages %}' redirect_from: - /articles/configuring-docker-for-use-with-github-package-registry diff --git a/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules.md b/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules.md new file mode 100644 index 0000000000..37dbc4337d --- /dev/null +++ b/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules.md @@ -0,0 +1,31 @@ +--- +title: Configuring tag protection rules +shortTitle: Configure tag rules +intro: You can configure tag protection rules for your repository to prevent contributors from creating or deleting tags. +product: '{% data reusables.gated-features.tag-protection-rules %}' +versions: + fpt: '*' + ghes: '>3.4' + ghae: 'issue-6337' + ghec: '*' +--- + +{% note %} + +**Note:** Tag protection rules are currently in beta and subject to change. + +{% endnote %} + +When you add a tag protection rule, all tags that match the pattern provided will be protected. Only users with admin or maintain permissions in the repository will be able to create protected tags, and only users with admin permissions in the repository will be able to delete protected tags. For more information, see "[Repository roles for an organization](/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization#permissions-for-each-role)." + +Additionally, you can create custom repository roles to allow other groups of users to create or delete tags that match tag protection rules. For more information, see "[Managing custom repository roles for an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization)." + +{% data reusables.repositories.navigate-to-repo %} +{% data reusables.repositories.sidebar-settings %} +1. In the "Code and automation" section of the sidebar, click **{% octicon "tag" aria-label="The tag icon" %} Tags**. +1. Click **New rule**. +![New tag protection rule](/assets/images/help/repository/new-tag-protection-rule.png) +1. Under "Tag name pattern", type the pattern of the tags you want to protect. In this example, typing "\*" protects all tags. +![Set tag protection pattern](/assets/images/help/repository/set-tag-protection-pattern.png) +1. Click **Add rule**. +![Add tag protection rule](/assets/images/help/repository/add-tag-protection-rule.png) diff --git a/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/index.md b/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/index.md index a1acf8fb29..f58f1de767 100644 --- a/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/index.md +++ b/content/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/index.md @@ -17,6 +17,7 @@ children: - /enabling-anonymous-git-read-access-for-a-repository - /about-email-notifications-for-pushes-to-your-repository - /configuring-autolinks-to-reference-external-resources + - /configuring-tag-protection-rules shortTitle: Manage repository settings --- diff --git a/data/features/reopen-dependabot-alerts.yml b/data/features/reopen-dependabot-alerts.yml new file mode 100644 index 0000000000..f6ff7fa597 --- /dev/null +++ b/data/features/reopen-dependabot-alerts.yml @@ -0,0 +1,6 @@ +# Reference 5861 +versions: + fpt: '*' + ghec: '*' + ghes: '>3.4' + ghae: 'issue-5861' diff --git a/data/reusables/gated-features/tag-protection-rules.md b/data/reusables/gated-features/tag-protection-rules.md new file mode 100644 index 0000000000..3f8d774374 --- /dev/null +++ b/data/reusables/gated-features/tag-protection-rules.md @@ -0,0 +1 @@ +{% ifversion ghae %}Tag protection rules are available in internal and private repositories with {% data variables.product.prodname_ghe_managed %}, {% else%}Tag protection rules are available {% endif %}in public repositories with {% data variables.product.prodname_free_user %} and {% data variables.product.prodname_free_team %} for organizations, and in public and private repositories with {% data variables.product.prodname_pro %}, {% data variables.product.prodname_team %}, {% data variables.product.prodname_ghe_cloud %}, and {% data variables.product.prodname_ghe_server %}. {% ifversion fpt or ghec %}{% data reusables.gated-features.more-info %}{% endif %} diff --git a/data/reusables/identity-and-permissions/ip-allow-lists-example-and-restrictions.md b/data/reusables/identity-and-permissions/ip-allow-lists-example-and-restrictions.md index 96f7dd9575..b0e648d57c 100644 --- a/data/reusables/identity-and-permissions/ip-allow-lists-example-and-restrictions.md +++ b/data/reusables/identity-and-permissions/ip-allow-lists-example-and-restrictions.md @@ -1 +1 @@ -For example, you can allow access from only the IP address of your office network. The allow list for IP addresses will block access via the web, API, and Git from any IP addresses that are not on the allow list. +For example, you can allow access from only the IP address of your office network. The allow list for IP addresses will block access to private resources via the web, API, and Git from any IP addresses that are not on the allow list. diff --git a/data/reusables/saml/cannot-update-existing-saml-settings.md b/data/reusables/saml/cannot-update-existing-saml-settings.md deleted file mode 100644 index 22983550ed..0000000000 --- a/data/reusables/saml/cannot-update-existing-saml-settings.md +++ /dev/null @@ -1,5 +0,0 @@ -{% note %} - -**Note:** When SAML SSO is enabled, the only setting you can update on {% data variables.product.prodname_dotcom %} for your existing SAML configuration is the SAML certificate. If you need to update the Sign on URL or Issuer, you must first disable SAML SSO and then reconfigure SAML SSO with the new settings. - -{% endnote %} diff --git a/data/reusables/scim/emu-scim-rate-limit.md b/data/reusables/scim/emu-scim-rate-limit.md new file mode 100644 index 0000000000..f6317a7545 --- /dev/null +++ b/data/reusables/scim/emu-scim-rate-limit.md @@ -0,0 +1,5 @@ +{% note %} + +**Note:** To avoid exceeding the rate limit on {% data variables.product.product_name %}, do not assign more than 1,000 users per hour to the IdP application. If you use groups to assign users to the IdP application, do not add more than 100 users to each group per hour. If you exceed these thresholds, attempts to provision users may fail with a "rate limit" error. + +{% endnote %} \ No newline at end of file diff --git a/lib/get-redirect.js b/lib/get-redirect.js index d9c5b5ec81..52810ac88d 100644 --- a/lib/get-redirect.js +++ b/lib/get-redirect.js @@ -9,9 +9,9 @@ const nonEnterpriseDefaultVersionPrefix = `/${nonEnterpriseDefaultVersion}` // Return the new URI if there is one, otherwise return undefined. export default function getRedirect(uri, context) { - const { redirects, pages } = context + const { redirects, userLanguage } = context - let language = 'en' + let language = userLanguage || 'en' let withoutLanguage = uri if (languagePrefixRegex.test(uri)) { language = uri.match(languagePrefixRegex)[1] @@ -109,12 +109,7 @@ export default function getRedirect(uri, context) { } if (basicCorrection) { - return ( - getRedirect(basicCorrection, { - redirects, - pages, - }) || basicCorrection - ) + return getRedirect(basicCorrection, context) || basicCorrection } if (withoutLanguage.startsWith('/admin/')) { diff --git a/middleware/detect-language.js b/middleware/detect-language.js index 1d222ede5b..9472e8d36c 100644 --- a/middleware/detect-language.js +++ b/middleware/detect-language.js @@ -1,14 +1,17 @@ -import libLanguages from '../lib/languages.js' +import languages, { languageKeys } from '../lib/languages.js' import parser from 'accept-language-parser' -const languageCodes = Object.keys(libLanguages) const chineseRegions = ['CN', 'HK'] +// This value is replicated in two places! See component. +// Note, the only reason this is exported is to benefit the tests. +export const PREFERRED_LOCALE_COOKIE_NAME = 'preferredlang' + function translationExists(language) { if (language.code === 'zh') { return chineseRegions.includes(language.region) } - return languageCodes.includes(language.code) + return languageKeys.includes(language.code) } function getLanguageCode(language) { @@ -17,33 +20,41 @@ function getLanguageCode(language) { function getUserLanguage(browserLanguages) { try { - let userLanguage = getLanguageCode(browserLanguages[0]) let numTopPreferences = 1 for (let lang = 0; lang < browserLanguages.length; lang++) { // If language has multiple regions, Chrome adds the non-region language to list if (lang > 0 && browserLanguages[lang].code !== browserLanguages[lang - 1].code) numTopPreferences++ if (translationExists(browserLanguages[lang]) && numTopPreferences < 3) { - userLanguage = getLanguageCode(browserLanguages[lang]) - break + return getLanguageCode(browserLanguages[lang]) } } - return userLanguage } catch { return undefined } } +function getUserLanguageFromCookie(req) { + const value = req.cookies[PREFERRED_LOCALE_COOKIE_NAME] + // But if it's a WIP language, reject it. + if (value && languages[value] && !languages[value].wip) { + return value + } +} + // determine language code from a path. Default to en if no valid match export function getLanguageCodeFromPath(path) { const maybeLanguage = (path.split('/')[path.startsWith('/_next/data/') ? 4 : 1] || '').slice(0, 2) - return languageCodes.includes(maybeLanguage) ? maybeLanguage : 'en' + return languageKeys.includes(maybeLanguage) ? maybeLanguage : 'en' } export default function detectLanguage(req, res, next) { req.language = getLanguageCodeFromPath(req.path) // Detecting browser language by user preference - const browserLanguages = parser.parse(req.headers['accept-language']) - req.userLanguage = getUserLanguage(browserLanguages) + req.userLanguage = getUserLanguageFromCookie(req) + if (!req.userLanguage) { + const browserLanguages = parser.parse(req.headers['accept-language']) + req.userLanguage = getUserLanguage(browserLanguages) + } return next() } diff --git a/middleware/redirects/handle-redirects.js b/middleware/redirects/handle-redirects.js index d799f161a5..76c2fb39ee 100644 --- a/middleware/redirects/handle-redirects.js +++ b/middleware/redirects/handle-redirects.js @@ -1,6 +1,6 @@ import patterns from '../../lib/patterns.js' import { URL } from 'url' -import languages, { pathLanguagePrefixed } from '../../lib/languages.js' +import { pathLanguagePrefixed } from '../../lib/languages.js' import getRedirect from '../../lib/get-redirect.js' import { cacheControlFactory } from '../cache-control.js' @@ -13,16 +13,7 @@ export default function handleRedirects(req, res, next) { // blanket redirects for languageless homepage if (req.path === '/') { - let language = 'en' - - // if set, redirect to user's preferred language translation or else English - if ( - req.context.userLanguage && - languages[req.context.userLanguage] && - !languages[req.context.userLanguage].wip - ) { - language = req.context.userLanguage - } + const language = getLanguage(req) // Undo the cookie setting that CSRF sets. res.removeHeader('set-cookie') @@ -70,17 +61,12 @@ export default function handleRedirects(req, res, next) { // needs to become `/en/authentication/connecting-to-github-with-ssh` const possibleRedirectTo = `/en${req.path}` if (possibleRedirectTo in req.context.pages) { - // As of Jan 2022 we always redirect to `/en` if the URL doesn't - // specify a language. ...except for the root home page (`/`). - // It's unfortunate but that's how it currently works. - // It's tracked in #1145 - // Perhaps a more ideal solution would be to do something similar to - // the code above for `req.path === '/'` where we look at the user - // agent for a header and/or cookie. + const language = getLanguage(req) + // Note, it's important to use `req.url` here and not `req.path` // because the full URL can contain query strings. // E.g. `/foo?json=breadcrumbs` - redirect = `/en${req.url}` + redirect = `/${language}${req.url}` } } @@ -106,12 +92,21 @@ export default function handleRedirects(req, res, next) { // do the redirect if the from-URL already had a language in it if (pathLanguagePrefixed(req.path)) { cacheControl(res) + } else { + noCacheControl(res) } const permanent = usePermanentRedirect(req) return res.redirect(permanent ? 301 : 302, redirect) } +function getLanguage(req, default_ = 'en') { + // req.context.userLanguage, if it truthy, is always a valid supported + // language. It's whatever was in the user's request but filtered + // based on non-WIP languages in lib/languages.js + return req.context.userLanguage || default_ +} + function usePermanentRedirect(req) { // If the redirect was to essentially swap `enterprise-server@latest` // for `enterprise-server@3.x` then, we definitely don't want to diff --git a/package-lock.json b/package-lock.json index 9e17a23685..5450c2fadc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "hast-util-to-string": "^2.0.0", "hastscript": "^7.0.2", "helmet": "^4.6.0", - "highlight.js": "11.2.0", + "highlight.js": "11.4.0", "highlightjs-curl": "^1.3.0", "highlightjs-graphql": "^1.0.2", "hot-shots": "^9.0.0", @@ -151,7 +151,7 @@ "mockdate": "^3.0.5", "nock": "^13.2.2", "nodemon": "^2.0.15", - "npm-merge-driver-install": "^2.0.2", + "npm-merge-driver-install": "^3.0.0", "postcss": "^8.4.6", "prettier": "^2.5.1", "replace": "^1.2.1", @@ -4779,17 +4779,6 @@ "string-width": "^4.1.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -8216,20 +8205,6 @@ "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/ensure-posix-path": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", @@ -10780,9 +10755,9 @@ } }, "node_modules/highlight.js": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.2.0.tgz", - "integrity": "sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz", + "integrity": "sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==", "engines": { "node": ">=12.0.0" } @@ -15942,32 +15917,17 @@ "integrity": "sha1-yWkcF0bFXc++VMvYvU/wQbwrUZ0=" }, "node_modules/npm-merge-driver-install": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-merge-driver-install/-/npm-merge-driver-install-2.0.2.tgz", - "integrity": "sha512-pUpOxwzMLm+uO+JtCaU0z0JakZjoyHLeGz4bqx56NNlZ4j2GDt//LkgyRoGeOEwK00hKKL6RUHYqdYL3A1r5mQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-merge-driver-install/-/npm-merge-driver-install-3.0.0.tgz", + "integrity": "sha512-NiwTYRDEhFK1Pl15jGB6GQNU1fN4kgYM/VMZgP6n19xBY8l+ed6xcK8C0J0+86o3noL5HKgJKZ1MUuMnxm7d5w==", "dev": true, - "hasInstallScript": true, - "dependencies": { - "is-ci": "^3.0.1" - }, "bin": { "npm-merge-driver-install": "src/install.js", + "npm-merge-driver-is-installed": "src/is-installed.js", "npm-merge-driver-merge": "src/merge.js", "npm-merge-driver-uninstall": "src/uninstall.js" } }, - "node_modules/npm-merge-driver-install/node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -18587,6 +18547,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/rehype-highlight/node_modules/highlight.js": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.3.1.tgz", + "integrity": "sha512-PUhCRnPjLtiLHZAQ5A/Dt5F8cWZeMyj9KRsACsWT+OD6OP0x6dp5OmT5jdx0JgEyPxPZZIPQpRN2TciUT7occw==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/rehype-highlight/node_modules/lowlight": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-2.4.0.tgz", @@ -26312,14 +26280,6 @@ "string-width": "^4.1.0" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "optional": true, - "peer": true - }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -29109,17 +29069,6 @@ "tapable": "^2.2.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "ensure-posix-path": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", @@ -31056,9 +31005,9 @@ "dev": true }, "highlight.js": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.2.0.tgz", - "integrity": "sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw==" + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz", + "integrity": "sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==" }, "highlightjs-curl": { "version": "1.3.0", @@ -34873,24 +34822,10 @@ "integrity": "sha1-yWkcF0bFXc++VMvYvU/wQbwrUZ0=" }, "npm-merge-driver-install": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-merge-driver-install/-/npm-merge-driver-install-2.0.2.tgz", - "integrity": "sha512-pUpOxwzMLm+uO+JtCaU0z0JakZjoyHLeGz4bqx56NNlZ4j2GDt//LkgyRoGeOEwK00hKKL6RUHYqdYL3A1r5mQ==", - "dev": true, - "requires": { - "is-ci": "^3.0.1" - }, - "dependencies": { - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - } - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-merge-driver-install/-/npm-merge-driver-install-3.0.0.tgz", + "integrity": "sha512-NiwTYRDEhFK1Pl15jGB6GQNU1fN4kgYM/VMZgP6n19xBY8l+ed6xcK8C0J0+86o3noL5HKgJKZ1MUuMnxm7d5w==", + "dev": true }, "npm-run-path": { "version": "4.0.1", @@ -36890,6 +36825,11 @@ "format": "^0.2.0" } }, + "highlight.js": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.3.1.tgz", + "integrity": "sha512-PUhCRnPjLtiLHZAQ5A/Dt5F8cWZeMyj9KRsACsWT+OD6OP0x6dp5OmT5jdx0JgEyPxPZZIPQpRN2TciUT7occw==" + }, "lowlight": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-2.4.0.tgz", diff --git a/package.json b/package.json index e746301a35..e43a16a77f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "hast-util-to-string": "^2.0.0", "hastscript": "^7.0.2", "helmet": "^4.6.0", - "highlight.js": "11.2.0", + "highlight.js": "11.4.0", "highlightjs-curl": "^1.3.0", "highlightjs-graphql": "^1.0.2", "hot-shots": "^9.0.0", @@ -153,7 +153,7 @@ "mockdate": "^3.0.5", "nock": "^13.2.2", "nodemon": "^2.0.15", - "npm-merge-driver-install": "^2.0.2", + "npm-merge-driver-install": "^3.0.0", "postcss": "^8.4.6", "prettier": "^2.5.1", "replace": "^1.2.1", diff --git a/tests/rendering/server.js b/tests/rendering/server.js index 63dd51be51..7530ecf527 100644 --- a/tests/rendering/server.js +++ b/tests/rendering/server.js @@ -632,7 +632,7 @@ describe('server', () => { expect(res.statusCode).toBe(302) expect(res.headers['set-cookie']).toBeUndefined() // no cache control because a language prefix had to be injected - expect(res.headers['cache-control']).toBeUndefined() + expect(res.headers['cache-control']).toBe('private, no-store') }) test('redirects old articles to their slugified URL', async () => { @@ -702,7 +702,8 @@ describe('server', () => { expect(res.statusCode).toBe(302) expect(res.headers.location.startsWith('/en/')).toBe(true) expect(res.headers['set-cookie']).toBeUndefined() - expect(res.headers['cache-control']).toBeUndefined() + // no cache control because a language prefix had to be injected + expect(res.headers['cache-control']).toBe('private, no-store') }) test('redirects that not only injects /en/ should have cache-control', async () => { diff --git a/tests/routing/redirects.js b/tests/routing/redirects.js index 27dd42623b..46ecd5c671 100644 --- a/tests/routing/redirects.js +++ b/tests/routing/redirects.js @@ -2,12 +2,14 @@ import { fileURLToPath } from 'url' import path from 'path' import { isPlainObject } from 'lodash-es' import supertest from 'supertest' +import { jest } from '@jest/globals' + import createApp from '../../lib/app.js' import enterpriseServerReleases from '../../lib/enterprise-server-releases.js' import Page from '../../lib/page.js' import { get } from '../helpers/supertest.js' import versionSatisfiesRange from '../../lib/version-satisfies-range.js' -import { jest } from '@jest/globals' +import { PREFERRED_LOCALE_COOKIE_NAME } from '../../middleware/detect-language.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -132,6 +134,28 @@ describe('redirects', () => { expect(res.headers.location).toBe('/ja') expect(res.headers['cache-control']).toBe('private, no-store') }) + test('homepage redirects to preferred language by cookie', async () => { + const res = await get('/', { + headers: { + Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`, + 'Accept-Language': 'es', // note how this is going to be ignored + }, + }) + expect(res.statusCode).toBe(302) + expect(res.headers.location).toBe('/ja') + expect(res.headers['cache-control']).toBe('private, no-store') + }) + test('homepage redirects to preferred language by cookie if valid', async () => { + const res = await get('/', { + headers: { + Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=xy`, + 'Accept-Language': 'ja', // note how this is going to be ignored + }, + }) + expect(res.statusCode).toBe(302) + expect(res.headers.location).toBe('/ja') + expect(res.headers['cache-control']).toBe('private, no-store') + }) }) describe('external redirects', () => { @@ -149,15 +173,69 @@ describe('redirects', () => { }) describe('localized redirects', () => { + const redirectFrom = + '/desktop/contributing-to-projects/changing-a-remote-s-url-from-github-desktop' + const redirectTo = + '/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/changing-a-remotes-url-from-github-desktop' + test('redirect_from for renamed pages', async () => { - const { res } = await get( - '/ja/desktop/contributing-to-projects/changing-a-remote-s-url-from-github-desktop' - ) + const { res } = await get(`/ja${redirectFrom}`) expect(res.statusCode).toBe(301) - const expected = - '/ja/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/changing-a-remotes-url-from-github-desktop' + const expected = `/ja${redirectTo}` expect(res.headers.location).toBe(expected) }) + + test('redirect_from for renamed pages by Accept-Language header', async () => { + const { res } = await get(redirectFrom, { + headers: { + 'Accept-Language': 'ja', + }, + }) + expect(res.statusCode).toBe(302) + const expected = `/ja${redirectTo}` + expect(res.headers.location).toBe(expected) + expect(res.headers['cache-control']).toBe('private, no-store') + }) + + test('redirect_from for renamed pages but ignore Accept-Language header if not recognized', async () => { + const { res } = await get(redirectFrom, { + headers: { + // None of these are recognized + 'Accept-Language': 'sv,fr,gr', + }, + }) + expect(res.statusCode).toBe(302) + const expected = `/en${redirectTo}` + expect(res.headers.location).toBe(expected) + expect(res.headers['cache-control']).toBe('private, no-store') + }) + + test('redirect_from for renamed pages but ignore unrecognized Accept-Language header values', async () => { + const { res } = await get(redirectFrom, { + headers: { + // Only the last one is recognized + 'Accept-Language': 'sv,ja', + }, + }) + expect(res.statusCode).toBe(302) + const expected = `/ja${redirectTo}` + expect(res.headers.location).toBe(expected) + expect(res.headers['cache-control']).toBe('private, no-store') + }) + + test('will inject the preferred language from cookie', async () => { + const { res } = await get(redirectFrom, { + headers: { + Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`, + 'Accept-Language': 'es', // note how this is going to be ignored + }, + }) + // 302 because the redirect depended on cookie + expect(res.statusCode).toBe(302) + const expected = `/ja${redirectTo}` + expect(res.headers.location).toBe(expected) + expect(res.headers['cache-control']).toBe('private, no-store') + }) }) describe('enterprise home page', () => { diff --git a/tests/unit/get-redirect.js b/tests/unit/get-redirect.js index 5ad969f6ed..ad425e0be3 100644 --- a/tests/unit/get-redirect.js +++ b/tests/unit/get-redirect.js @@ -148,4 +148,18 @@ describe('getRedirect basics', () => { // it already has the enterprise-server prefix. expect(getRedirect('/enterprise-server/foo', ctx)).toBe(`/en/enterprise-server@${latest}/bar`) }) + + it('should redirect according to the req.context.userLanguage', () => { + const ctx = { + pages: {}, + redirects: { + '/foo': '/bar', + }, + userLanguage: 'ja', + } + expect(getRedirect('/foo', ctx)).toBe(`/ja/bar`) + // falls back to 'en' if it's falsy + ctx.userLanguage = null + expect(getRedirect('/foo', ctx)).toBe(`/en/bar`) + }) })