huridocs/uwazi

View on GitHub
app/api/authorization.v2/services/specs/AuthorizationService.spec.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { MongoPermissionsDataSource } from 'api/authorization.v2/database/MongoPermissionsDataSource';
import { UnauthorizedError } from 'api/authorization.v2/errors/UnauthorizedError';
import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant';
import { Relationship } from 'api/relationships.v2/model/Relationship';
import { MongoRelationshipsDataSource } from 'api/relationships.v2/database/MongoRelationshipsDataSource';
import { User, UserRole } from 'api/users.v2/model/User';
import { getFixturesFactory } from 'api/utils/fixturesFactory';
import { testingEnvironment } from 'api/utils/testingEnvironment';
import { DBFixture } from 'api/utils/testing_db';
import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults';
import { AccessLevels, AuthorizationService } from '../AuthorizationService';

const factory = getFixturesFactory();

const fixtures: DBFixture = {
  relationTypes: [factory.relationType('relType1')],
  entities: [
    factory.entity('entity1', undefined, undefined, {
      permissions: [
        { refId: factory.id('user1'), level: 'write', type: 'user' },
        { refId: factory.id('user2'), level: 'read', type: 'user' },
      ],
    }),
    factory.entity('entity2', undefined, undefined, {
      permissions: [
        { refId: factory.id('user2'), level: 'write', type: 'user' },
        { refId: factory.id('group1'), level: 'read', type: 'group' },
      ],
    }),
    factory.entity('entity3', undefined, undefined, {
      permissions: [{ refId: factory.id('group2'), level: 'write', type: 'group' }],
      published: true,
    }),
    factory.entity('entity4', undefined, undefined, {
      published: true,
    }),
    factory.entity('entity5', undefined, undefined, {
      permissions: [{ refId: factory.id('user1'), level: 'write', type: 'user' }],
    }),
  ],
  relationships: [
    factory.v2.database.relationshipDBO('rel12', 'entity1', 'entity2', 'relType1'),
    factory.v2.database.relationshipDBO('rel15', 'entity1', 'entity5', 'relType1'),
    factory.v2.database.relationshipDBO('rel23', 'entity2', 'entity3', 'relType1'),
    factory.v2.database.relationshipDBO('rel34', 'entity3', 'entity4', 'relType1'),
  ],
};

beforeAll(async () => {
  await testingEnvironment.setUp(fixtures);
});

afterAll(async () => {
  await testingEnvironment.tearDown();
});

describe("When there's no authenticated user", () => {
  describe('and the entity is not public', () => {
    it('should return false', async () => {
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        undefined
      );
      expect(await auth.isAuthorized('read', ['entity1'])).toBe(false);
      expect(await auth.isAuthorized('write', ['entity1'])).toBe(false);
    });

    it('should throw an error on validation', async () => {
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        undefined
      );
      await expect(async () => auth.validateAccess('read', ['entity1'])).rejects.toThrow(
        UnauthorizedError
      );
      await expect(async () => auth.validateAccess('write', ['entity2'])).rejects.toThrow(
        UnauthorizedError
      );
    });
  });

  describe('and the entity is public', () => {
    it('should only allow to read', async () => {
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        undefined
      );
      expect(await auth.isAuthorized('read', ['entity3'])).toBe(true);
      expect(await auth.isAuthorized('write', ['entity3'])).toBe(false);
    });
  });

  describe('and not all the entities are public', () => {
    it('should not allow to read nor write', async () => {
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        undefined
      );
      expect(await auth.isAuthorized('read', ['entity3', 'entity1'])).toBe(false);
      expect(await auth.isAuthorized('write', ['entity3', 'entity1'])).toBe(false);
    });
  });

  it('should allow empty read', async () => {
    const auth = new AuthorizationService(
      new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
      undefined
    );
    expect(await auth.isAuthorized('read', [])).toBe(true);
    expect(await auth.isAuthorized('write', [])).toBe(false);
  });
});

describe("When there's an authenticated user", () => {
  describe.each(['admin', 'editor'] as const)('and the user is %s', role => {
    it('should return true', async () => {
      const adminUser = new User(factory.id('admin').toHexString(), role, []);
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        adminUser
      );
      expect(await auth.isAuthorized('read', ['entity1'])).toBe(true);
      expect(await auth.isAuthorized('read', [])).toBe(true);
      expect(await auth.isAuthorized('write', ['entity1'])).toBe(true);
      expect(await auth.isAuthorized('write', [])).toBe(true);
    });

    it('should not throw an error', async () => {
      const adminUser = new User(factory.id('admin').toHexString(), role, []);
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        adminUser
      );

      await auth.validateAccess('read', ['entity1']);
      await auth.validateAccess('write', ['entity1']);
    });
  });

  describe('and the user is collaborator', () => {
    type Case1 = { user: string; entities: string[]; level: 'read' | 'write'; result: boolean };
    it.each<Case1>([
      { user: 'user1', entities: ['entity1'], level: 'write', result: true },
      { user: 'user1', entities: ['entity1'], level: 'read', result: true },
      { user: 'user1', entities: ['entity2'], level: 'read', result: false },
      { user: 'user2', entities: ['entity1'], level: 'write', result: false },
      { user: 'user2', entities: ['entity1'], level: 'read', result: true },
      { user: 'user1', entities: ['entity1', 'entity2'], level: 'write', result: false },
      { user: 'user1', entities: ['entity1', 'entity2'], level: 'read', result: false },
      { user: 'user2', entities: ['entity1', 'entity2'], level: 'write', result: false },
      { user: 'user2', entities: ['entity1', 'entity2'], level: 'read', result: true },
      { user: 'user1', entities: ['entity3'], level: 'read', result: true },
      { user: 'user1', entities: ['entity3'], level: 'write', result: false },
      { user: 'user1', entities: ['entity1', 'entity3'], level: 'read', result: true },
      { user: 'user1', entities: ['entity1', 'entity3'], level: 'write', result: false },
      { user: 'user1', entities: ['entity2', 'entity3'], level: 'read', result: false },
      { user: 'user1', entities: ['entity2', 'entity3'], level: 'write', result: false },
      { user: 'user1', entities: [], level: 'read', result: true },
      { user: 'user1', entities: [], level: 'write', result: true },
    ])(
      'should return [$result] if [$user] wants to [$level] from/to $entities',
      async ({ user, entities, level, result }) => {
        const auth = new AuthorizationService(
          new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
          new User(factory.id(user).toHexString(), 'collaborator', [])
        );

        expect(await auth.isAuthorized(level, entities)).toBe(result);
      }
    );

    type Case2 = { entities: string[]; level: 'read' | 'write'; result: boolean };
    it.each<Case2>([
      { entities: ['entity2', 'entity3'], level: 'read', result: true },
      { entities: ['entity3'], level: 'write', result: true },
    ])('should consider the user groups', async ({ entities, level, result }) => {
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        new User(factory.id('grouped user').toHexString(), 'collaborator', [
          factory.id('group1').toHexString(),
          factory.id('group2').toHexString(),
        ])
      );

      expect(await auth.isAuthorized(level, entities)).toBe(result);
    });
  });
});

describe('filterEntities()', () => {
  it.each<{
    user: User | undefined;
    usertype: string;
    level: AccessLevels;
    expectedResult: string[];
  }>([
    {
      user: new User(factory.id('user2').toHexString(), 'collaborator', []),
      usertype: 'collaborator',
      level: 'read',
      expectedResult: ['entity1', 'entity2', 'entity3'],
    },
    {
      user: new User(factory.id('user2').toHexString(), 'collaborator', []),
      usertype: 'collaborator',
      level: 'write',
      expectedResult: ['entity2'],
    },
    {
      user: new User(factory.id('user1').toHexString(), 'editor', []),
      usertype: 'editor',
      level: 'read',
      expectedResult: ['entity1', 'entity2', 'entity3'],
    },
    {
      user: new User(factory.id('user1').toHexString(), 'editor', []),
      usertype: 'editor',
      level: 'write',
      expectedResult: ['entity1', 'entity2', 'entity3'],
    },
    {
      user: new User(factory.id('user1').toHexString(), 'admin', []),
      usertype: 'admin',
      level: 'read',
      expectedResult: ['entity1', 'entity2', 'entity3'],
    },
    {
      user: new User(factory.id('user1').toHexString(), 'admin', []),
      usertype: 'admin',
      level: 'write',
      expectedResult: ['entity1', 'entity2', 'entity3'],
    },
    {
      user: undefined,
      usertype: 'undefined',
      level: 'read',
      expectedResult: ['entity3'],
    },
    {
      user: undefined,
      usertype: 'undefined',
      level: 'write',
      expectedResult: [],
    },
  ])(
    'should filter entities for a/an $usertype to $level',
    async ({ user, level, expectedResult }) => {
      const auth = new AuthorizationService(
        new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
        user
      );

      const filtered = await auth.filterEntities(level, ['entity1', 'entity2', 'entity3']);

      expect(filtered).toEqual(expectedResult);
    }
  );
});

describe('filterRelationships()', () => {
  let allRelationships: Relationship[];

  beforeAll(async () => {
    const ds = new MongoRelationshipsDataSource(getConnection(), DefaultTransactionManager());
    allRelationships = await ds.getAll().all();
  });

  it.each<{
    case: string;
    user: User | undefined;
    level: AccessLevels;
    expectedIds: string[];
  }>([
    {
      case: 'unauthenticated user read',
      user: undefined,
      level: 'read',
      expectedIds: [factory.idString('rel34')],
    },
    {
      case: 'unauthenticated user write',
      user: undefined,
      level: 'write',
      expectedIds: [],
    },
    {
      case: 'collaborator read',
      user: new User(factory.idString('user1'), 'collaborator', []),
      level: 'read',
      expectedIds: [factory.idString('rel15'), factory.idString('rel34')],
    },
    {
      case: 'collaborator write',
      user: new User(factory.idString('user1'), 'collaborator', []),
      level: 'write',
      expectedIds: [factory.idString('rel15')],
    },
  ])('should filter for $case', async ({ user, level, expectedIds }) => {
    const auth = new AuthorizationService(
      new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
      user
    );

    const filtered = await auth.filterRelationships(allRelationships, level);
    const ids = filtered.map(r => r._id);

    expect(ids).toEqual(expectedIds);
  });

  it.each<{
    role: UserRole;
    level: AccessLevels;
  }>([
    { role: 'admin', level: 'read' },
    { role: 'admin', level: 'write' },
    { role: 'editor', level: 'read' },
    { role: 'editor', level: 'write' },
  ])('$role should be able to $level everything', async ({ role: username, level }) => {
    const user = new User(factory.idString(username), username, []);
    const auth = new AuthorizationService(
      new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()),
      user
    );

    const filtered = await auth.filterRelationships(allRelationships, level);

    expect(filtered).toHaveLength(allRelationships.length);
  });
});