mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Merge pull request #467 from beejay141/test/CommonServer/UserAuthorization
test: added unit test for file CommonServer/Middleware/UserAuthorization
This commit is contained in:
@@ -28,6 +28,7 @@ import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import SsoAuthorizationException from 'Common/Types/Exception/SsoAuthorizationException';
|
||||
import JSONWebTokenData from 'Common/Types/JsonWebTokenData';
|
||||
import logger from '../Utils/Logger';
|
||||
import Exception from 'Common/Types/Exception/Exception';
|
||||
|
||||
export default class UserMiddleware {
|
||||
/*
|
||||
@@ -153,6 +154,7 @@ export default class UserMiddleware {
|
||||
oneuptimeRequest.userType = UserType.User;
|
||||
}
|
||||
|
||||
const { userId: userObjectId } = oneuptimeRequest.userAuthorization;
|
||||
const userId: string =
|
||||
oneuptimeRequest.userAuthorization.userId.toString();
|
||||
|
||||
@@ -175,121 +177,40 @@ export default class UserMiddleware {
|
||||
}
|
||||
|
||||
if (tenantId) {
|
||||
const project: Project | null = await ProjectService.findOneById({
|
||||
id: tenantId,
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const userTenantAccessPermission: UserTenantAccessPermission | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionWithTenantId(
|
||||
req,
|
||||
tenantId,
|
||||
userObjectId
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('Invalid tenantId')
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
project.requireSsoForLogin &&
|
||||
!UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
tenantId,
|
||||
new ObjectID(userId)
|
||||
)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new SsoAuthorizationException()
|
||||
);
|
||||
}
|
||||
|
||||
// get project level permissions if projectid exists in request.
|
||||
const userTenantAccessPermission: UserTenantAccessPermission | null =
|
||||
await AccessTokenService.getUserTenantAccessPermission(
|
||||
oneuptimeRequest.userAuthorization.userId,
|
||||
tenantId
|
||||
);
|
||||
|
||||
if (userTenantAccessPermission) {
|
||||
oneuptimeRequest.userTenantAccessPermission = {};
|
||||
oneuptimeRequest.userTenantAccessPermission[
|
||||
tenantId.toString()
|
||||
] = userTenantAccessPermission;
|
||||
if (userTenantAccessPermission) {
|
||||
oneuptimeRequest.userTenantAccessPermission = {};
|
||||
oneuptimeRequest.userTenantAccessPermission[
|
||||
tenantId.toString()
|
||||
] = userTenantAccessPermission;
|
||||
}
|
||||
} catch (error) {
|
||||
return Response.sendErrorResponse(req, res, error as Exception);
|
||||
}
|
||||
}
|
||||
|
||||
if (req.headers['is-multi-tenant-query']) {
|
||||
oneuptimeRequest.userTenantAccessPermission = {};
|
||||
|
||||
if (
|
||||
userGlobalAccessPermission &&
|
||||
userGlobalAccessPermission.projectIds &&
|
||||
userGlobalAccessPermission.projectIds.length > 0
|
||||
) {
|
||||
const projects: Array<Project> = await ProjectService.findBy({
|
||||
query: {
|
||||
_id: QueryHelper.in(
|
||||
userGlobalAccessPermission?.projectIds.map(
|
||||
(i: ObjectID) => {
|
||||
return i.toString();
|
||||
}
|
||||
) || []
|
||||
),
|
||||
},
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const projectId of userGlobalAccessPermission?.projectIds ||
|
||||
[]) {
|
||||
// check if the force sso login is required. and if it is, then check then token.
|
||||
|
||||
if (
|
||||
projects.find((p: Project) => {
|
||||
return (
|
||||
p._id === projectId.toString() &&
|
||||
p.requireSsoForLogin
|
||||
);
|
||||
}) &&
|
||||
!UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
new ObjectID(userId)
|
||||
)
|
||||
) {
|
||||
// Add default permissions.
|
||||
const userTenantAccessPermission: UserTenantAccessPermission | null =
|
||||
AccessTokenService.getDefaultUserTenantAccessPermission(
|
||||
projectId
|
||||
);
|
||||
oneuptimeRequest.userTenantAccessPermission[
|
||||
projectId.toString()
|
||||
] = userTenantAccessPermission;
|
||||
} else {
|
||||
// get project level permissions if projectid exists in request.
|
||||
const userTenantAccessPermission: UserTenantAccessPermission | null =
|
||||
await AccessTokenService.getUserTenantAccessPermission(
|
||||
oneuptimeRequest.userAuthorization.userId,
|
||||
projectId
|
||||
);
|
||||
|
||||
if (userTenantAccessPermission) {
|
||||
oneuptimeRequest.userTenantAccessPermission[
|
||||
projectId.toString()
|
||||
] = userTenantAccessPermission;
|
||||
}
|
||||
}
|
||||
const userTenantAccessPermission: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userObjectId,
|
||||
userGlobalAccessPermission.projectIds
|
||||
);
|
||||
if (userTenantAccessPermission) {
|
||||
oneuptimeRequest.userTenantAccessPermission =
|
||||
userTenantAccessPermission;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,4 +264,106 @@ export default class UserMiddleware {
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
public static async getUserTenantAccessPermissionWithTenantId(
|
||||
req: ExpressRequest,
|
||||
tenantId: ObjectID,
|
||||
userId: ObjectID
|
||||
): Promise<UserTenantAccessPermission | null> {
|
||||
const project: Project | null = await ProjectService.findOneById({
|
||||
id: tenantId,
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new BadDataException('Invalid tenantId');
|
||||
}
|
||||
|
||||
if (
|
||||
project.requireSsoForLogin &&
|
||||
!UserMiddleware.doesSsoTokenForProjectExist(req, tenantId, userId)
|
||||
) {
|
||||
throw new SsoAuthorizationException();
|
||||
}
|
||||
|
||||
// get project level permissions if projectid exists in request.
|
||||
return await AccessTokenService.getUserTenantAccessPermission(
|
||||
userId,
|
||||
tenantId
|
||||
);
|
||||
}
|
||||
|
||||
public static async getUserTenantAccessPermissionForMultiTenant(
|
||||
req: ExpressRequest,
|
||||
userId: ObjectID,
|
||||
projectIds: ObjectID[]
|
||||
): Promise<Dictionary<UserTenantAccessPermission> | null> {
|
||||
if (!projectIds.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projects: Array<Project> = await ProjectService.findBy({
|
||||
query: {
|
||||
_id: QueryHelper.in(
|
||||
projectIds.map((i: ObjectID) => {
|
||||
return i.toString();
|
||||
}) || []
|
||||
),
|
||||
},
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
let result: Dictionary<UserTenantAccessPermission> | null = null;
|
||||
for (const projectId of projectIds) {
|
||||
// check if the force sso login is required. and if it is, then check then token.
|
||||
|
||||
let userTenantAccessPermission: UserTenantAccessPermission | null;
|
||||
if (
|
||||
projects.find((p: Project) => {
|
||||
return (
|
||||
p._id === projectId.toString() && p.requireSsoForLogin
|
||||
);
|
||||
}) &&
|
||||
!UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
)
|
||||
) {
|
||||
// Add default permissions.
|
||||
userTenantAccessPermission =
|
||||
AccessTokenService.getDefaultUserTenantAccessPermission(
|
||||
projectId
|
||||
);
|
||||
} else {
|
||||
// get project level permissions if projectid exists in request.
|
||||
userTenantAccessPermission =
|
||||
await AccessTokenService.getUserTenantAccessPermission(
|
||||
userId,
|
||||
projectId
|
||||
);
|
||||
}
|
||||
|
||||
if (userTenantAccessPermission) {
|
||||
if (!result) {
|
||||
result = {};
|
||||
}
|
||||
result[projectId.toString()] = userTenantAccessPermission;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
831
CommonServer/Tests/Middleware/UserAuthorization.test.ts
Normal file
831
CommonServer/Tests/Middleware/UserAuthorization.test.ts
Normal file
@@ -0,0 +1,831 @@
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import UserMiddleware from '../../Middleware/UserAuthorization';
|
||||
import {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
NextFunction,
|
||||
} from '../../Utils/Express';
|
||||
import JSONWebToken from '../../Utils/JsonWebToken';
|
||||
import logger from '../../Utils/Logger';
|
||||
import JSONWebTokenData from 'Common/Types/JsonWebTokenData';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import ProjectMiddleware from '../../Middleware/ProjectAuthorization';
|
||||
import UserService from '../../Services/UserService';
|
||||
import Email from 'Common/Types/Email';
|
||||
import ProjectService from '../../Services/ProjectService';
|
||||
import Response from '../../Utils/Response';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Project from 'Model/Models/Project';
|
||||
import SsoAuthorizationException from 'Common/Types/Exception/SsoAuthorizationException';
|
||||
import AccessTokenService from '../../Services/AccessTokenService';
|
||||
import {
|
||||
UserGlobalAccessPermission,
|
||||
UserTenantAccessPermission,
|
||||
} from 'Common/Types/Permission';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import HashedString from 'Common/Types/HashedString';
|
||||
|
||||
jest.mock('../../Utils/Logger');
|
||||
jest.mock('../../Middleware/ProjectAuthorization');
|
||||
jest.mock('../../Utils/JsonWebToken');
|
||||
jest.mock('../../Services/UserService');
|
||||
jest.mock('../../Services/AccessTokenService');
|
||||
jest.mock('../../Utils/Response');
|
||||
jest.mock('../../Services/ProjectService');
|
||||
jest.mock('Common/Types/HashedString');
|
||||
jest.mock('Common/Types/JSONFunctions');
|
||||
|
||||
type StringOrNull = string | null;
|
||||
|
||||
describe('UserMiddleware', () => {
|
||||
const mockedAccessToken: string = ObjectID.generate().toString();
|
||||
const projectId: ObjectID = ObjectID.generate();
|
||||
const userId: ObjectID = ObjectID.generate();
|
||||
const mockedProject: Project = { _id: projectId.toString() } as Project;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getAccessToken', () => {
|
||||
test('should return access token when authorization token is passed in the request header', () => {
|
||||
const req: ExpressRequest = {
|
||||
headers: { authorization: mockedAccessToken },
|
||||
query: {},
|
||||
} as ExpressRequest;
|
||||
|
||||
const result: StringOrNull = UserMiddleware.getAccessToken(req);
|
||||
|
||||
expect(result).toEqual(mockedAccessToken);
|
||||
});
|
||||
|
||||
test('should return access token when accessToken token is passed in the request query', () => {
|
||||
const req: Partial<ExpressRequest> = {
|
||||
query: { accessToken: mockedAccessToken },
|
||||
headers: {},
|
||||
};
|
||||
|
||||
const result: StringOrNull = UserMiddleware.getAccessToken(
|
||||
req as ExpressRequest
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockedAccessToken);
|
||||
});
|
||||
|
||||
test('should split and return the access token part of a bearer authorization token', () => {
|
||||
const req: ExpressRequest = {
|
||||
headers: { authorization: `Bearer ${mockedAccessToken}` },
|
||||
query: {},
|
||||
} as ExpressRequest;
|
||||
|
||||
const result: StringOrNull = UserMiddleware.getAccessToken(req);
|
||||
|
||||
expect(result).toEqual(mockedAccessToken);
|
||||
});
|
||||
|
||||
test('should return null when authorization nor accessToken is passed', () => {
|
||||
const req: ExpressRequest = {
|
||||
headers: {},
|
||||
query: {},
|
||||
} as ExpressRequest;
|
||||
|
||||
const result: StringOrNull = UserMiddleware.getAccessToken(req);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSsoTokens', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const req: Partial<ExpressRequest> = {
|
||||
headers: { 'sso-token': mockedAccessToken },
|
||||
};
|
||||
|
||||
test('should return an empty object when ssoToken is not passed', () => {
|
||||
const result: Dictionary<string> = UserMiddleware.getSsoTokens({
|
||||
headers: {},
|
||||
} as ExpressRequest);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
test('should return an empty object when ssoToken cannot be decoded', () => {
|
||||
const error: Error = new Error('Invalid token');
|
||||
const spyDecode: jest.SpyInstance = jest
|
||||
.spyOn(JSONWebToken, 'decode')
|
||||
.mockImplementationOnce((_: string) => {
|
||||
throw error;
|
||||
});
|
||||
const spyErrorLogger: jest.SpyInstance = jest.spyOn(
|
||||
logger,
|
||||
'error'
|
||||
);
|
||||
|
||||
const result: Dictionary<string> = UserMiddleware.getSsoTokens(
|
||||
req as ExpressRequest
|
||||
);
|
||||
|
||||
expect(result).toEqual({});
|
||||
expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken);
|
||||
expect(spyErrorLogger).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
test("should return an empty object when the decoded sso-token object doesn't have projectId property", () => {
|
||||
const spyDecode: jest.SpyInstance = jest
|
||||
.spyOn(JSONWebToken, 'decode')
|
||||
.mockReturnValueOnce({} as JSONWebTokenData);
|
||||
const spyErrorLogger: jest.SpyInstance = jest.spyOn(
|
||||
logger,
|
||||
'error'
|
||||
);
|
||||
|
||||
const result: Dictionary<string> = UserMiddleware.getSsoTokens(
|
||||
req as ExpressRequest
|
||||
);
|
||||
|
||||
expect(result).toEqual({});
|
||||
expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken);
|
||||
expect(spyErrorLogger).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('should return a dictionary of string with projectId key', () => {
|
||||
jest.spyOn(JSONWebToken, 'decode').mockReturnValueOnce({
|
||||
projectId,
|
||||
} as JSONWebTokenData);
|
||||
|
||||
const result: Dictionary<string> = UserMiddleware.getSsoTokens(
|
||||
req as ExpressRequest
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
[projectId.toString()]: mockedAccessToken,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('doesSsoTokenForProjectExist', () => {
|
||||
const req: ExpressRequest = {} as ExpressRequest;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(UserMiddleware, 'getSsoTokens').mockReturnValue({
|
||||
[projectId.toString()]: mockedAccessToken,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return false, when getSsoTokens does not return a value', () => {
|
||||
const spyGetSsoTokens: jest.SpyInstance = jest
|
||||
.spyOn(UserMiddleware, 'getSsoTokens')
|
||||
.mockImplementationOnce(jest.fn().mockReturnValue(null));
|
||||
|
||||
const result: boolean = UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
expect(spyGetSsoTokens).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should return false, when getSsoTokens returns a dictionary that does not contain the projectId's value as key", () => {
|
||||
const spyGetSsoTokens: jest.SpyInstance = jest
|
||||
.spyOn(UserMiddleware, 'getSsoTokens')
|
||||
.mockReturnValueOnce({});
|
||||
|
||||
const result: boolean = UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
expect(spyGetSsoTokens).toHaveBeenCalledWith(req);
|
||||
});
|
||||
|
||||
test("should return false, when decoded JWT object's projectId value does not match with projectId passed as parameter", () => {
|
||||
const objectId: ObjectID = ObjectID.generate();
|
||||
|
||||
const spyDecode: jest.SpyInstance = jest
|
||||
.spyOn(JSONWebToken, 'decode')
|
||||
.mockReturnValueOnce({
|
||||
projectId: objectId,
|
||||
userId,
|
||||
} as JSONWebTokenData);
|
||||
|
||||
const result: boolean = UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken);
|
||||
});
|
||||
|
||||
test("should return false, when decoded JWT object's userId does not match with userId passed as parameter", () => {
|
||||
const objectId: ObjectID = ObjectID.generate();
|
||||
|
||||
const spyDecode: jest.SpyInstance = jest
|
||||
.spyOn(JSONWebToken, 'decode')
|
||||
.mockReturnValueOnce({
|
||||
userId: objectId,
|
||||
projectId,
|
||||
} as JSONWebTokenData);
|
||||
|
||||
const result: boolean = UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken);
|
||||
});
|
||||
|
||||
test('should return true', () => {
|
||||
jest.spyOn(JSONWebToken, 'decode').mockReturnValueOnce({
|
||||
userId,
|
||||
projectId,
|
||||
} as JSONWebTokenData);
|
||||
|
||||
const result: boolean = UserMiddleware.doesSsoTokenForProjectExist(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserMiddleware', () => {
|
||||
let req: ExpressRequest;
|
||||
let res: ExpressResponse;
|
||||
const next: NextFunction = jest.fn();
|
||||
|
||||
const hashValue: string = 'hash-value';
|
||||
const mockedTenantAccessPermission: UserTenantAccessPermission = {
|
||||
projectId,
|
||||
} as UserTenantAccessPermission;
|
||||
const mockedGlobalAccessPermission: UserGlobalAccessPermission = {
|
||||
projectIds: [projectId],
|
||||
} as UserGlobalAccessPermission;
|
||||
|
||||
const jwtTokenData: JSONWebTokenData = {
|
||||
userId,
|
||||
isMasterAdmin: true,
|
||||
email: new Email('test@gmail.com'),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValue(
|
||||
projectId
|
||||
);
|
||||
jest.spyOn(ProjectMiddleware, 'hasApiKey').mockReturnValue(false);
|
||||
jest.spyOn(UserMiddleware, 'getAccessToken').mockReturnValue(
|
||||
mockedAccessToken
|
||||
);
|
||||
jest.spyOn(JSONWebToken, 'decode').mockReturnValue(jwtTokenData);
|
||||
jest.spyOn(HashedString, 'hashValue').mockResolvedValue(hashValue);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
req = { headers: {} } as ExpressRequest;
|
||||
|
||||
res = {} as ExpressResponse;
|
||||
res.set = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should call isValidProjectIdAndApiKeyMiddleware and return when hasApiKey returns true', async () => {
|
||||
jest.spyOn(ProjectMiddleware, 'hasApiKey').mockReturnValueOnce(
|
||||
true
|
||||
);
|
||||
|
||||
const spyGetAccessToken: jest.SpyInstance = jest.spyOn(
|
||||
UserMiddleware,
|
||||
'getAccessToken'
|
||||
);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(
|
||||
ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware
|
||||
).toHaveBeenCalledWith(req, res, next);
|
||||
expect(spyGetAccessToken).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should call function 'next' and return, when getAccessToken returns a null value", async () => {
|
||||
const spyGetAccessToken: jest.SpyInstance = jest
|
||||
.spyOn(UserMiddleware, 'getAccessToken')
|
||||
.mockReturnValueOnce(null);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(spyGetAccessToken).toHaveBeenCalledWith(req);
|
||||
expect(JSONWebToken.decode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should call function 'next' and return, when accessToken can not be decoded", async () => {
|
||||
const error: Error = new Error('Invalid access token');
|
||||
|
||||
const spyJWTDecode: jest.SpyInstance = jest
|
||||
.spyOn(JSONWebToken, 'decode')
|
||||
.mockImplementationOnce((_: string) => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(spyJWTDecode).toHaveBeenCalledWith(mockedAccessToken);
|
||||
expect(UserService.updateOneBy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should set global-permissions and global-permissions-hash in the response header, when user has global access permission', async () => {
|
||||
jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValueOnce(
|
||||
null
|
||||
);
|
||||
jest.spyOn(JSONFunctions, 'serialize').mockReturnValueOnce({});
|
||||
const spyGetUserGlobalAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserGlobalAccessPermission')
|
||||
.mockResolvedValueOnce(mockedGlobalAccessPermission);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
'global-permissions',
|
||||
JSON.stringify({})
|
||||
);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
'global-permissions-hash',
|
||||
hashValue
|
||||
);
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(
|
||||
userId
|
||||
);
|
||||
});
|
||||
|
||||
test('should not set global-permissions and global-permissions-hash in the response header, when user does not have global access permission', async () => {
|
||||
jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValueOnce(
|
||||
null
|
||||
);
|
||||
const spyGetUserGlobalAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserGlobalAccessPermission')
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(res.set).not.toHaveBeenCalledWith(
|
||||
'global-permissions',
|
||||
expect.anything()
|
||||
);
|
||||
expect(res.set).not.toHaveBeenCalledWith(
|
||||
'global-permissions-hash',
|
||||
expect.anything()
|
||||
);
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(
|
||||
userId
|
||||
);
|
||||
});
|
||||
|
||||
test('should call Response.sendErrorResponse, when tenantId is passed in the header and getUserTenantAccessPermissionWithTenantId throws an exception', async () => {
|
||||
const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance =
|
||||
jest
|
||||
.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionWithTenantId'
|
||||
)
|
||||
.mockRejectedValueOnce(new SsoAuthorizationException());
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(Response.sendErrorResponse).toHaveBeenCalledWith(
|
||||
req,
|
||||
res,
|
||||
new SsoAuthorizationException()
|
||||
);
|
||||
expect(
|
||||
spyGetUserTenantAccessPermissionWithTenantId
|
||||
).toHaveBeenCalledWith(req, projectId, userId);
|
||||
expect(next).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('should set project-permissions and project-permissions-hash in the response header, when tenantId is passed in the header and user has tenant access permission', async () => {
|
||||
jest.spyOn(JSONFunctions, 'serialize').mockReturnValueOnce({});
|
||||
const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance =
|
||||
jest
|
||||
.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionWithTenantId'
|
||||
)
|
||||
.mockResolvedValueOnce(mockedTenantAccessPermission);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(req, res, next);
|
||||
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
'project-permissions',
|
||||
JSON.stringify({})
|
||||
);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
'project-permissions-hash',
|
||||
hashValue
|
||||
);
|
||||
expect(next).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
spyGetUserTenantAccessPermissionWithTenantId
|
||||
).toHaveBeenCalledWith(req, projectId, userId);
|
||||
});
|
||||
|
||||
test("should not call getUserTenantAccessPermissionForMultiTenant, when is-multi-tenant-query is set in the request header and but userGlobalAccessPermission's projectIds length is zero", async () => {
|
||||
const mockedRequest: Partial<ExpressRequest> = {
|
||||
headers: { 'is-multi-tenant-query': 'yes' },
|
||||
};
|
||||
|
||||
jest.spyOn(
|
||||
AccessTokenService,
|
||||
'getUserGlobalAccessPermission'
|
||||
).mockResolvedValueOnce({
|
||||
...mockedGlobalAccessPermission,
|
||||
projectIds: [],
|
||||
});
|
||||
jest.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionWithTenantId'
|
||||
).mockResolvedValueOnce(null);
|
||||
const spyGetUserTenantAccessPermissionForMultiTenant: jest.SpyInstance =
|
||||
jest.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionForMultiTenant'
|
||||
);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(
|
||||
mockedRequest as ExpressRequest,
|
||||
res,
|
||||
next
|
||||
);
|
||||
|
||||
expect(res.set).not.toHaveBeenCalledWith(
|
||||
'project-permissions',
|
||||
expect.anything()
|
||||
);
|
||||
expect(res.set).not.toHaveBeenCalledWith(
|
||||
'project-permissions-hash',
|
||||
expect.anything()
|
||||
);
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(
|
||||
spyGetUserTenantAccessPermissionForMultiTenant
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should set project-permissions and project-permissions-hash in the response header, when is-multi-tenant-query is set in the request header and getUserTenantAccessPermissionForMultiTenant returned access permission', async () => {
|
||||
const mockedRequest: Partial<ExpressRequest> = {
|
||||
headers: { 'is-multi-tenant-query': 'yes' },
|
||||
};
|
||||
|
||||
jest.spyOn(JSONFunctions, 'serialize').mockReturnValue({});
|
||||
jest.spyOn(
|
||||
AccessTokenService,
|
||||
'getUserGlobalAccessPermission'
|
||||
).mockResolvedValueOnce(mockedGlobalAccessPermission);
|
||||
jest.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionWithTenantId'
|
||||
).mockResolvedValueOnce(null);
|
||||
const spyGetUserTenantAccessPermissionForMultiTenant: jest.SpyInstance =
|
||||
jest
|
||||
.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionForMultiTenant'
|
||||
)
|
||||
.mockResolvedValueOnce({
|
||||
[projectId.toString()]: mockedTenantAccessPermission,
|
||||
});
|
||||
|
||||
await UserMiddleware.getUserMiddleware(
|
||||
mockedRequest as ExpressRequest,
|
||||
res,
|
||||
next
|
||||
);
|
||||
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
'project-permissions',
|
||||
JSON.stringify({})
|
||||
);
|
||||
expect(res.set).toHaveBeenCalledWith(
|
||||
'project-permissions-hash',
|
||||
hashValue
|
||||
);
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(
|
||||
spyGetUserTenantAccessPermissionForMultiTenant
|
||||
).toHaveBeenCalledWith(
|
||||
mockedRequest,
|
||||
userId,
|
||||
mockedGlobalAccessPermission.projectIds
|
||||
);
|
||||
});
|
||||
|
||||
test('should not set project-permissions and project-permissions-hash in the response header, when the project-permissions-hash set in the request header equals the projectPermissionsHash computed from userTenantAccessPermission', async () => {
|
||||
const mockedRequest: Partial<ExpressRequest> = {
|
||||
headers: { 'project-permissions-hash': hashValue },
|
||||
};
|
||||
|
||||
const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance =
|
||||
jest
|
||||
.spyOn(
|
||||
UserMiddleware,
|
||||
'getUserTenantAccessPermissionWithTenantId'
|
||||
)
|
||||
.mockResolvedValueOnce(mockedTenantAccessPermission);
|
||||
|
||||
await UserMiddleware.getUserMiddleware(
|
||||
mockedRequest as ExpressRequest,
|
||||
res,
|
||||
next
|
||||
);
|
||||
|
||||
expect(res.set).not.toHaveBeenCalledWith(
|
||||
'project-permissions',
|
||||
expect.anything()
|
||||
);
|
||||
expect(res.set).not.toHaveBeenCalledWith(
|
||||
'project-permissions-hash',
|
||||
expect.anything()
|
||||
);
|
||||
expect(next).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
spyGetUserTenantAccessPermissionWithTenantId
|
||||
).toHaveBeenCalledWith(mockedRequest, projectId, userId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserTenantAccessPermissionWithTenantId', () => {
|
||||
const req: ExpressRequest = {} as ExpressRequest;
|
||||
|
||||
const spyFindOneById: jest.SpyInstance = jest.spyOn(
|
||||
ProjectService,
|
||||
'findOneById'
|
||||
);
|
||||
const spyDoesSsoTokenForProjectExist: jest.SpyInstance = jest.spyOn(
|
||||
UserMiddleware,
|
||||
'doesSsoTokenForProjectExist'
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should throw 'Invalid tenantId' error, when project is not found for the tenantId", async () => {
|
||||
spyFindOneById.mockResolvedValueOnce(null);
|
||||
|
||||
await expect(
|
||||
UserMiddleware.getUserTenantAccessPermissionWithTenantId(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
)
|
||||
).rejects.toThrowError(new BadDataException('Invalid tenantId'));
|
||||
expect(spyFindOneById).toHaveBeenCalledWith({
|
||||
id: projectId,
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw SsoAuthorizationException error, when sso login is required but sso token for the projectId does not exist', async () => {
|
||||
spyFindOneById.mockResolvedValueOnce({
|
||||
...mockedProject,
|
||||
requireSsoForLogin: true,
|
||||
});
|
||||
|
||||
spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false);
|
||||
|
||||
await expect(
|
||||
UserMiddleware.getUserTenantAccessPermissionWithTenantId(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
)
|
||||
).rejects.toThrowError(new SsoAuthorizationException());
|
||||
expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
});
|
||||
|
||||
test('should return null when getUserTenantAccessPermission returns null', async () => {
|
||||
spyFindOneById.mockResolvedValueOnce(mockedProject);
|
||||
|
||||
const spyGetUserTenantAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserTenantAccessPermission')
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
const result: UserTenantAccessPermission | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionWithTenantId(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(spyGetUserTenantAccessPermission).toHaveBeenLastCalledWith(
|
||||
userId,
|
||||
projectId
|
||||
);
|
||||
});
|
||||
|
||||
test('should return UserTenantAccessPermission', async () => {
|
||||
const mockedUserTenantAccessPermission: UserTenantAccessPermission =
|
||||
{ projectId } as UserTenantAccessPermission;
|
||||
|
||||
spyFindOneById.mockResolvedValueOnce(mockedProject);
|
||||
|
||||
const spyGetUserTenantAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserTenantAccessPermission')
|
||||
.mockResolvedValueOnce(mockedUserTenantAccessPermission);
|
||||
|
||||
const result: UserTenantAccessPermission | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionWithTenantId(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockedUserTenantAccessPermission);
|
||||
expect(spyGetUserTenantAccessPermission).toHaveBeenLastCalledWith(
|
||||
userId,
|
||||
projectId
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserTenantAccessPermissionForMultiTenant', () => {
|
||||
const req: ExpressRequest = {} as ExpressRequest;
|
||||
const mockedUserTenantAccessPermission: UserTenantAccessPermission = {
|
||||
projectId,
|
||||
} as UserTenantAccessPermission;
|
||||
|
||||
const spyFindBy: jest.SpyInstance = jest.spyOn(
|
||||
ProjectService,
|
||||
'findBy'
|
||||
);
|
||||
const spyDoesSsoTokenForProjectExist: jest.SpyInstance = jest.spyOn(
|
||||
UserMiddleware,
|
||||
'doesSsoTokenForProjectExist'
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should return null, when projectIds length is zero', async () => {
|
||||
const result: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userId,
|
||||
[]
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(spyFindBy).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('should return default tenant access permission, when project for a projectId is found, sso is required for login, but sso token does not exist for that projectId', async () => {
|
||||
spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false);
|
||||
spyFindBy.mockResolvedValueOnce([
|
||||
{ ...mockedProject, requireSsoForLogin: true },
|
||||
]);
|
||||
|
||||
const spyGetDefaultUserTenantAccessPermission: jest.SpyInstance =
|
||||
jest
|
||||
.spyOn(
|
||||
AccessTokenService,
|
||||
'getDefaultUserTenantAccessPermission'
|
||||
)
|
||||
.mockReturnValueOnce(mockedUserTenantAccessPermission);
|
||||
|
||||
const result: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userId,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
[projectId.toString()]: mockedUserTenantAccessPermission,
|
||||
});
|
||||
expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith(
|
||||
req,
|
||||
projectId,
|
||||
userId
|
||||
);
|
||||
expect(
|
||||
spyGetDefaultUserTenantAccessPermission
|
||||
).toHaveBeenCalledWith(projectId);
|
||||
});
|
||||
|
||||
test('should return user tenant access permission, when project for a projectId is found, sso is not required for login and project level permission exist for the projectId', async () => {
|
||||
spyFindBy.mockResolvedValueOnce([mockedProject]);
|
||||
|
||||
const spyGetUserTenantAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserTenantAccessPermission')
|
||||
.mockResolvedValueOnce(mockedUserTenantAccessPermission);
|
||||
|
||||
const result: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userId,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
[projectId.toString()]: mockedUserTenantAccessPermission,
|
||||
});
|
||||
expect(spyDoesSsoTokenForProjectExist).not.toBeCalled();
|
||||
expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith(
|
||||
userId,
|
||||
projectId
|
||||
);
|
||||
});
|
||||
|
||||
test('should return null, when project for a projectId is found, sso is not required for login but project level permission does not exist for the projectId', async () => {
|
||||
spyFindBy.mockResolvedValueOnce([mockedProject]);
|
||||
|
||||
const spyGetUserTenantAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserTenantAccessPermission')
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
const result: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userId,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith(
|
||||
userId,
|
||||
projectId
|
||||
);
|
||||
});
|
||||
|
||||
test('should return user tenant access permission, when project for a projectId is not found, but project level permission exist for the projectId', async () => {
|
||||
spyFindBy.mockResolvedValueOnce([]);
|
||||
|
||||
jest.spyOn(
|
||||
AccessTokenService,
|
||||
'getUserTenantAccessPermission'
|
||||
).mockResolvedValueOnce(mockedUserTenantAccessPermission);
|
||||
|
||||
const result: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userId,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
[projectId.toString()]: mockedUserTenantAccessPermission,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return null, when project for a projectId is not found, and project level permission does not exist for the projectId', async () => {
|
||||
spyFindBy.mockResolvedValueOnce([]);
|
||||
|
||||
const spyGetUserTenantAccessPermission: jest.SpyInstance = jest
|
||||
.spyOn(AccessTokenService, 'getUserTenantAccessPermission')
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
const result: Dictionary<UserTenantAccessPermission> | null =
|
||||
await UserMiddleware.getUserTenantAccessPermissionForMultiTenant(
|
||||
req,
|
||||
userId,
|
||||
[projectId]
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith(
|
||||
userId,
|
||||
projectId
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user