]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16566 Handle max token lifetime setting when downgrading
authorJeremy Davis <jeremy.davis@sonarsource.com>
Thu, 7 Jul 2022 15:21:08 +0000 (17:21 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 8 Jul 2022 20:02:47 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx
server/sonar-web/src/main/js/types/settings.ts

index cf48ac0401565bd9ae7fc604d8136247beac98ec..3afaf428006c092e4277f850b1ff87d3437ccfbc 100644 (file)
@@ -31,6 +31,7 @@ import { mockUserToken } from '../../../helpers/mocks/token';
 import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
 import { renderApp } from '../../../helpers/testReactTestingUtils';
 import { Permissions } from '../../../types/permissions';
+import { SettingsKey } from '../../../types/settings';
 import { TokenExpiration, TokenType } from '../../../types/token';
 import { CurrentUser } from '../../../types/users';
 import routes from '../routes';
@@ -46,10 +47,12 @@ jest.mock('../../../helpers/dates', () => {
 });
 
 jest.mock('../../../api/settings', () => {
+  const { SettingsKey } = jest.requireActual('../../../types/settings');
   return {
     ...jest.requireActual('../../../api/settings'),
     getValues: jest.fn().mockResolvedValue([
       {
+        key: SettingsKey.TokenMaxAllowedLifetime,
         value: 'No expiration'
       }
     ])
@@ -269,8 +272,8 @@ describe('security page', () => {
   ])(
     'should display expiration date inferior or equal to the settings limit %s',
     async (settingMaxLifetime, expectedTime, notExpectedTime) => {
-      (getValues as jest.Mock).mockImplementation(() =>
-        Promise.resolve([{ value: settingMaxLifetime }])
+      (getValues as jest.Mock).mockImplementationOnce(() =>
+        Promise.resolve([{ key: SettingsKey.TokenMaxAllowedLifetime, value: settingMaxLifetime }])
       );
 
       renderAccountApp(
@@ -293,6 +296,29 @@ describe('security page', () => {
     }
   );
 
+  it('should handle absent setting', async () => {
+    (getValues as jest.Mock).mockImplementationOnce(() => Promise.resolve([]));
+
+    renderAccountApp(
+      mockLoggedInUser({ permissions: { global: [Permissions.Scan] } }),
+      securityPagePath
+    );
+
+    await selectEvent.openMenu(screen.getAllByRole('textbox')[2]);
+
+    [
+      TokenExpiration.OneMonth,
+      TokenExpiration.ThreeMonths,
+      TokenExpiration.OneYear,
+      TokenExpiration.NoExpiration
+    ].forEach(time => {
+      // TokenExpiration.OneMonth is expected twice has it is the default value.
+      expect(screen.getAllByText(`users.tokens.expiration.${time}`).length).toBe(
+        time === TokenExpiration.OneMonth ? 2 : 1
+      );
+    });
+  });
+
   it.each([
     [TokenExpiration.OneMonth, '2022-07-01'],
     [TokenExpiration.ThreeMonths, '2022-08-30'],
index d80749b2c75d17ba2faa530d1bb4f643a77aa129..cff1307a7c226b7d7e5b3d4e964db3e95294d0d4 100644 (file)
@@ -30,6 +30,7 @@ import { now, toShortNotSoISOString } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
 import { hasGlobalPermission } from '../../../helpers/users';
 import { Permissions } from '../../../types/permissions';
+import { SettingsKey } from '../../../types/settings';
 import { TokenExpiration, TokenType, UserToken } from '../../../types/token';
 import { CurrentUser } from '../../../types/users';
 import TokensFormItem, { TokenDeleteConfirmation } from './TokensFormItem';
@@ -119,16 +120,25 @@ export class TokensForm extends React.PureComponent<Props, State> {
   };
 
   fetchTokenSettings = async () => {
-    const setting = await getValues({ keys: 'sonar.auth.token.max.allowed.lifetime' });
-    if (setting === undefined || setting[0].value === undefined) {
+    /*
+     * We intentionally fetch all settings, because fetching a specific setting will
+     * return it from the DB as a fallback, even if the setting is not defined at startup.
+     */
+    const settings = await getValues({ keys: '' });
+    const maxTokenLifetime = settings.find(
+      ({ key }) => key === SettingsKey.TokenMaxAllowedLifetime
+    );
+
+    if (maxTokenLifetime === undefined || maxTokenLifetime.value === undefined) {
       return;
     }
-    const maxTokenLifetime = setting[0].value;
-    if (SETTINGS_EXPIRATION_MAP[maxTokenLifetime] !== TokenExpiration.NoExpiration) {
+
+    const maxTokenExpirationOption = SETTINGS_EXPIRATION_MAP[maxTokenLifetime.value];
+
+    if (maxTokenExpirationOption !== TokenExpiration.NoExpiration) {
       const tokenExpirationOptions = EXPIRATION_OPTIONS.filter(
         option =>
-          option.value <= SETTINGS_EXPIRATION_MAP[maxTokenLifetime] &&
-          option.value !== TokenExpiration.NoExpiration
+          option.value <= maxTokenExpirationOption && option.value !== TokenExpiration.NoExpiration
       );
       if (this.mounted) {
         this.setState({ tokenExpirationOptions });
index a9b9b5f610a9fae5bad7d4994a16c423ff505641..9374cf76253451ba312a3edabdf57544e274b8f4 100644 (file)
@@ -25,7 +25,8 @@ export const enum SettingsKey {
   DefaultProjectVisibility = 'projects.default.visibility',
   ServerBaseUrl = 'sonar.core.serverBaseURL',
   PluginRiskConsent = 'sonar.plugins.risk.consent',
-  LicenceRemainingLocNotificationThreshold = 'sonar.license.notifications.remainingLocThreshold'
+  LicenceRemainingLocNotificationThreshold = 'sonar.license.notifications.remainingLocThreshold',
+  TokenMaxAllowedLifetime = 'sonar.auth.token.max.allowed.lifetime'
 }
 
 export enum GlobalSettingKeys {