Skip to content
This repository has been archived by the owner on Dec 6, 2024. It is now read-only.

Commit

Permalink
fix: Do not allow users to change root password (#503)
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyen102 authored May 26, 2021
1 parent 8c30378 commit a436f73
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ class CreateRootUserService extends Service {
});
this.log.info('Created root user in the data lake');

await dbPasswordService.savePassword(getSystemRequestContext(), {
await dbPasswordService.saveRootPassword(getSystemRequestContext(), {
uid: createdUser.uid,
username: rootUserName,
password: rootUserPassword,
});
this.log.info("Created root user's password");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const ServicesContainer = require('@aws-ee/base-services-container/lib/services-container');
const Boom = require('@aws-ee/base-services-container/lib/boom');

const DbServiceMock = require('@aws-ee/base-services/lib/db-service');
const SettingsServiceMock = require('@aws-ee/base-services/lib/settings/env-settings-service');
const DbPasswordService = require('../db-password-service');

// Mocked dependencies
jest.mock('@aws-ee/base-services/lib/db-service');
jest.mock('@aws-ee/base-services/lib/settings/env-settings-service');

describe('db-password-service', () => {
let service = null;
let dbService = null;
let settingService = null;
const settingMockFunction = jest.fn();
beforeAll(async () => {
const container = new ServicesContainer();
container.register('dbService', new DbServiceMock());
container.register('dbPasswordService', new DbPasswordService());
container.register('settings', new SettingsServiceMock());

await container.initServices();

service = await container.find('dbPasswordService');
dbService = await container.find('dbService');
settingService = await container.find('settings');

settingMockFunction.mockReturnValue('root');
settingService.get = settingMockFunction;

service.assertAuthorized = jest.fn();
});

const adminNonSystemRequestContext = {
actions: [],
resources: [],
authenticated: true,
principal: {
uid: 'abcd',
username: 'user1',
ns: 'internal',
isAdmin: true,
userRole: 'admin',
status: 'active',
},
attr: {},
principalIdentifier: { uid: 'abcd' },
};

const systemRequestContext = {
actions: [],
resources: [],
authenticated: true,
principal: {
uid: '_system_',
username: '_system_',
ns: 'internal',
isAdmin: true,
userRole: 'admin',
status: 'active',
},
attr: {},
principalIdentifier: { uid: '_system_' },
};

afterEach(() => {
expect(settingMockFunction.mock.calls[0][0]).toBe('rootUserName');
});

describe('saveRootPassword', () => {
it('should allow system to save root password', async () => {
// OPERATE
await service.saveRootPassword(systemRequestContext, { uid: 'abcd', password: 'fakePassword' });

// CHECK
expect(dbService.table.update).toHaveBeenCalled();
});

it('should NOT allow non-system user to save root password', async () => {
// OPERATE & CHECK
await expect(
service.saveRootPassword(adminNonSystemRequestContext, { uid: 'abcd', password: 'fakePassword' }),
).rejects.toEqual(new Boom().badRequest("'root' password can only be changed by 'system' user", true));
});
});

describe('savePassword', () => {
it('should allow admin to save password', async () => {
// OPERATE
await service.savePassword(adminNonSystemRequestContext, {
username: 'John',
uid: 'abcd',
password: 'fakePassword',
});

// CHECK
expect(dbService.table.update).toHaveBeenCalled();
});

it('should NOT allow admin to save root password', async () => {
// OPERATE & CHECK
await expect(
service.savePassword(adminNonSystemRequestContext, {
username: 'root',
uid: 'abcd',
password: 'fakePassword',
}),
).rejects.toEqual(new Boom().badRequest("'root' password can not be changed", true));
});

it('should NOT allow system to save root password', async () => {
// OPERATE & CHECK
await expect(
service.savePassword(systemRequestContext, {
username: 'root',
uid: 'abcd',
password: 'fakePassword',
}),
).rejects.toEqual(new Boom().badRequest("'root' password can not be changed", true));
});

it('should NOT allow non current user to save password', async () => {
const nonAdminRequestContext = {
actions: [],
resources: [],
authenticated: true,
principal: {
uid: 'xyz',
username: 'user2',
ns: 'internal',
isAdmin: false,
userRole: 'admin',
status: 'active',
},
attr: {},
principalIdentifier: { uid: 'xyz' },
};

// OPERATE & CHECK
await expect(
service.savePassword(nonAdminRequestContext, {
username: 'fakeUser',
uid: 'abcd',
password: 'fakePassword',
}),
).rejects.toEqual(new Boom().forbidden('You are not authorized to perform this operation', true));
});

it('should allow current user to save password', async () => {
const nonAdminRequestContext = {
actions: [],
resources: [],
authenticated: true,
principal: {
uid: 'abcd',
username: 'user2',
ns: 'internal',
isAdmin: false,
userRole: 'admin',
status: 'active',
},
attr: {},
principalIdentifier: { uid: 'abcd' },
};

// OPERATE
await service.savePassword(nonAdminRequestContext, {
username: 'user2',
uid: 'abcd',
password: 'fakePassword',
});

// CHECK
expect(dbService.table.update).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const uuid = require('uuid/v4');
const Service = require('@aws-ee/base-services-container/lib/service');
const { ensureCurrentUserOrAdmin } = require('../authorization/assertions');
const { runAndCatch } = require('../helpers/utils');
const { isSystem } = require('../authorization/authorization-utils');

const settingKeys = {
tableName: 'dbPasswords',
Expand All @@ -45,10 +46,27 @@ class DbPasswordService extends Service {
}
}

async saveRootPassword(requestContext, { password, uid }) {
const username = this.settings.get('rootUserName');
if (!isSystem(requestContext)) {
throw this.boom.badRequest("'root' password can only be changed by 'system' user", true);
}
await this.savePasswordHelper(username, password, uid);
}

async savePassword(requestContext, { username, password, uid }) {
// Allow only current user or admin to update (or create) the user's password
await ensureCurrentUserOrAdmin(requestContext, { uid });

// Don't allow users to change root user's password
if (username === this.settings.get('rootUserName')) {
throw this.boom.badRequest("'root' password can not be changed", true);
}

await this.savePasswordHelper(username, password, uid);
}

async savePasswordHelper(username, password, uid) {
const isValidPassword = await this.passwordMatchesPasswordPolicy(password);
if (!isValidPassword) {
throw this.boom.badRequest(
Expand Down
1 change: 1 addition & 0 deletions addons/addon-base/packages/services/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"validatorjs": "^3.18.1"
},
"devDependencies": {
"@aws-ee/base-services": "workspace:*",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.10.0",
Expand Down
1 change: 1 addition & 0 deletions main/solution/backend/config/infra/functions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ apiHandler:
APP_ENV_TYPE_CONFIGS_BUCKET_NAME: ${self:custom.settings.envTypeConfigsBucketName}
APP_ENV_BOOTSTRAP_BUCKET_NAME: ${self:custom.settings.environmentsBootstrapBucketName}
APP_CUSTOM_USER_AGENT: ${self:custom.settings.customUserAgent}
APP_ROOT_USER_NAME: ${self:custom.settings.rootUserName}

workflowLoopRunner:
handler: src/lambdas/workflow-loop-runner/handler.handler
Expand Down
5 changes: 3 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a436f73

Please sign in to comment.