Skip to content

Commit

Permalink
Review#1: improve tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin committed Feb 27, 2020
1 parent dd03c4f commit caed9b3
Show file tree
Hide file tree
Showing 8 changed files with 993 additions and 1,075 deletions.
153 changes: 70 additions & 83 deletions x-pack/plugins/security/server/authentication/providers/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@ import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
import { mockAuthenticationProviderOptions } from './base.mock';

import { IClusterClient, ScopeableRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
import { BasicAuthenticationProvider } from './basic';

function generateAuthorizationHeader(username: string, password: string) {
return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
}

function expectAuthenticateCall(
mockClusterClient: jest.Mocked<IClusterClient>,
scopeableRequest: ScopeableRequest
) {
expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);

const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
}

describe('BasicAuthenticationProvider', () => {
let provider: BasicAuthenticationProvider;
let mockOptions: ReturnType<typeof mockAuthenticationProviderOptions>;
Expand All @@ -32,22 +47,16 @@ describe('BasicAuthenticationProvider', () => {
mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);

const authenticationResult = await provider.login(
httpServerMock.createKibanaRequest({ headers: {} }),
credentials
await expect(
provider.login(httpServerMock.createKibanaRequest({ headers: {} }), credentials)
).resolves.toEqual(
AuthenticationResult.succeeded(user, {
authHeaders: { authorization },
state: { authorization },
})
);

expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
headers: { authorization },
});
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');

expect(authenticationResult.succeeded()).toBe(true);
expect(authenticationResult.user).toEqual(user);
expect(authenticationResult.state).toEqual({ authorization });
expect(authenticationResult.authHeaders).toEqual({ authorization });
expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
});

it('fails if user cannot be retrieved during login attempt', async () => {
Expand All @@ -60,75 +69,71 @@ describe('BasicAuthenticationProvider', () => {
mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);

const authenticationResult = await provider.login(request, credentials);
await expect(provider.login(request, credentials)).resolves.toEqual(
AuthenticationResult.failed(authenticationError)
);

expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
headers: { authorization },
});
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
expectAuthenticateCall(mockOptions.client, { headers: { authorization } });

expect(request.headers).not.toHaveProperty('authorization');
expect(authenticationResult.failed()).toBe(true);
expect(authenticationResult.user).toBeUndefined();
expect(authenticationResult.state).toBeUndefined();
expect(authenticationResult.error).toEqual(authenticationError);
});
});

describe('`authenticate` method', () => {
it('does not redirect AJAX requests that can not be authenticated to the login page.', async () => {
// Add `kbn-xsrf` header to make `can_redirect_request` think that it's AJAX request and
// avoid triggering of redirect logic.
const authenticationResult = await provider.authenticate(
httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }),
null
);

expect(authenticationResult.notHandled()).toBe(true);
await expect(
provider.authenticate(
httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }),
null
)
).resolves.toEqual(AuthenticationResult.notHandled());
});

it('redirects non-AJAX requests that can not be authenticated to the login page.', async () => {
const authenticationResult = await provider.authenticate(
httpServerMock.createKibanaRequest({ path: '/s/foo/some-path # that needs to be encoded' }),
null
);

expect(authenticationResult.redirected()).toBe(true);
expect(authenticationResult.redirectURL).toBe(
'/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded'
await expect(
provider.authenticate(
httpServerMock.createKibanaRequest({
path: '/s/foo/some-path # that needs to be encoded',
}),
null
)
).resolves.toEqual(
AuthenticationResult.redirectTo(
'/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded'
)
);
});

it('does not handle authentication if state exists, but authorization property is missing.', async () => {
const authenticationResult = await provider.authenticate(
httpServerMock.createKibanaRequest(),
{}
);
expect(authenticationResult.notHandled()).toBe(true);
await expect(
provider.authenticate(httpServerMock.createKibanaRequest(), {})
).resolves.toEqual(AuthenticationResult.notHandled());
});

it('does not handle authentication via `authorization` header.', async () => {
const authorization = generateAuthorizationHeader('user', 'password');
const request = httpServerMock.createKibanaRequest({ headers: { authorization } });

const authenticationResult = await provider.authenticate(request);
await expect(provider.authenticate(request)).resolves.toEqual(
AuthenticationResult.notHandled()
);

expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
expect(request.headers.authorization).toBe(authorization);
expect(authenticationResult.notHandled()).toBe(true);
});

it('does not handle authentication via `authorization` header even if state contains valid credentials.', async () => {
const authorization = generateAuthorizationHeader('user', 'password');
const request = httpServerMock.createKibanaRequest({ headers: { authorization } });

const authenticationResult = await provider.authenticate(request, { authorization });
await expect(provider.authenticate(request, { authorization })).resolves.toEqual(
AuthenticationResult.notHandled()
);

expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
expect(request.headers.authorization).toBe(authorization);
expect(authenticationResult.notHandled()).toBe(true);
});

it('succeeds if only state is available.', async () => {
Expand All @@ -140,19 +145,11 @@ describe('BasicAuthenticationProvider', () => {
mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);

const authenticationResult = await provider.authenticate(request, { authorization });

expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
headers: { authorization },
});
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
await expect(provider.authenticate(request, { authorization })).resolves.toEqual(
AuthenticationResult.succeeded(user, { authHeaders: { authorization } })
);

expect(authenticationResult.succeeded()).toBe(true);
expect(authenticationResult.user).toEqual(user);
expect(authenticationResult.state).toBeUndefined();
expect(authenticationResult.authHeaders).toEqual({ authorization });
expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
});

it('fails if state contains invalid credentials.', async () => {
Expand All @@ -164,40 +161,30 @@ describe('BasicAuthenticationProvider', () => {
mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);

const authenticationResult = await provider.authenticate(request, { authorization });
await expect(provider.authenticate(request, { authorization })).resolves.toEqual(
AuthenticationResult.failed(authenticationError)
);

expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
headers: { authorization },
});
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
expectAuthenticateCall(mockOptions.client, { headers: { authorization } });

expect(request.headers).not.toHaveProperty('authorization');
expect(authenticationResult.failed()).toBe(true);
expect(authenticationResult.user).toBeUndefined();
expect(authenticationResult.state).toBeUndefined();
expect(authenticationResult.authHeaders).toBeUndefined();
expect(authenticationResult.error).toBe(authenticationError);
});
});

describe('`logout` method', () => {
it('always redirects to the login page.', async () => {
const request = httpServerMock.createKibanaRequest();
const deauthenticateResult = await provider.logout(request);
expect(deauthenticateResult.redirected()).toBe(true);
expect(deauthenticateResult.redirectURL).toBe('/base-path/login?msg=LOGGED_OUT');
await expect(provider.logout(httpServerMock.createKibanaRequest())).resolves.toEqual(
DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT')
);
});

it('passes query string parameters to the login page.', async () => {
const request = httpServerMock.createKibanaRequest({
query: { next: '/app/ml', msg: 'SESSION_EXPIRED' },
});
const deauthenticateResult = await provider.logout(request);
expect(deauthenticateResult.redirected()).toBe(true);
expect(deauthenticateResult.redirectURL).toBe(
'/base-path/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED'
await expect(
provider.logout(
httpServerMock.createKibanaRequest({ query: { next: '/app/ml', msg: 'SESSION_EXPIRED' } })
)
).resolves.toEqual(
DeauthenticationResult.redirectTo('/base-path/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED')
);
});
});
Expand Down
Loading

0 comments on commit caed9b3

Please sign in to comment.