Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
30e28c9
Add OpenID Connect auth provider and unit tests
jkakavas Feb 4, 2019
bc44f5a
Address feedback
jkakavas Feb 5, 2019
a8493ec
add a subset of api integration tests
jkakavas Feb 5, 2019
82d8e0d
Convert provider to typescript
jkakavas May 6, 2019
b12f0da
Fix tests
jkakavas May 6, 2019
4315f8c
Do not handle simultaneous logins
jkakavas May 6, 2019
d3a36e1
fix tests
jkakavas May 6, 2019
eac2720
Add OpenID Connect integration tests
jkakavas May 7, 2019
ee62c3c
Fix behavior for 3rd party initiated logins
jkakavas May 8, 2019
ba05360
lint fixes
jkakavas May 8, 2019
ee8b25b
fix import
jkakavas May 8, 2019
c6c6d32
debug CI xsrf params
jkakavas May 8, 2019
7d07bd5
Fix properties and reenable CI jobs
jkakavas May 8, 2019
dbbf430
Merge remote-tracking branch 'origin/master' into oidc-auth-provider2
jkakavas May 8, 2019
85b63d8
Merge remote-tracking branch 'origin/master' into oidc-auth-provider2
jkakavas May 9, 2019
54130f5
Address review feedback
jkakavas May 10, 2019
510b40d
Merge remote-tracking branch 'origin/master' into oidc-auth-provider2
jkakavas May 10, 2019
561e2c1
Merge remote-tracking branch 'origin/master' into oidc-auth-provider2
jkakavas May 16, 2019
bda87a9
Address feedback round
jkakavas May 18, 2019
2c7f1e2
remove unnecessary default authc object
jkakavas May 18, 2019
c4c8707
Merge remote-tracking branch 'origin/master' into oidc-auth-provider2
jkakavas May 18, 2019
91be8e1
Making ProviderSpecificOptions a separate argument
kobelb May 20, 2019
a1c828e
Removing provider specific options from base mock
kobelb May 20, 2019
881baf7
Adding .default so `authc` and `oidc` are always objects
kobelb May 20, 2019
ea41784
Making oidc realm required when using oidc auth provider
kobelb May 20, 2019
ef7b9d7
Working around limitation of `config.has` and Joi's alternatives
kobelb May 20, 2019
629198f
Revising comment, no longer merged
kobelb May 20, 2019
e9ea111
Removing generic parameter we aren't using anymore
kobelb May 20, 2019
0ed1955
No longer using a default for authc
kobelb May 21, 2019
ca0e89b
Merge pull request #1 from kobelb/oidc-auth-provider2
jkakavas May 21, 2019
9c90e8a
Merge remote-tracking branch 'origin/master' into oidc-auth-provider2
jkakavas May 21, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions x-pack/plugins/security/__snapshots__/index.test.js.snap

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

9 changes: 9 additions & 0 deletions x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ export const security = (kibana) => new kibana.Plugin({
audit: Joi.object({
enabled: Joi.boolean().default(false)
}).default(),
authc: Joi.object({})
.when('authProviders', {
is: Joi.array().items(Joi.string().valid('oidc').required(), Joi.string()),
then: Joi.object({
oidc: Joi.object({
realm: Joi.string().required(),
}).default()
}).default()
})
}).default();
},

Expand Down
76 changes: 68 additions & 8 deletions x-pack/plugins/security/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,76 @@
import { security } from './index';
import { getConfigSchema } from '../../test_utils';

const describeWithContext = describe.each([
[{ dist: false }],
[{ dist: true }]
]);
const describeWithContext = describe.each([[{ dist: false }], [{ dist: true }]]);

describeWithContext('config schema with context %j', (context) => {
describeWithContext('config schema with context %j', context => {
it('produces correct config', async () => {
const schema = await getConfigSchema(security);
await expect(
schema.validate({}, { context })
).resolves.toMatchSnapshot();
await expect(schema.validate({}, { context })).resolves.toMatchSnapshot();
});
});

describe('config schema', () => {
describe('authc', () => {
describe('oidc', () => {
describe('realm', () => {
it(`returns a validation error when authProviders is "['oidc']" and realm is unspecified`, async () => {
const schema = await getConfigSchema(security);
const validationResult = schema.validate({
authProviders: ['oidc'],
});
expect(validationResult.error).toMatchSnapshot();
});

it(`is valid when authProviders is "['oidc']" and realm is specified`, async () => {
const schema = await getConfigSchema(security);
const validationResult = schema.validate({
authProviders: ['oidc'],
authc: {
oidc: {
realm: 'realm-1',
},
},
});
expect(validationResult.error).toBeNull();
expect(validationResult.value).toHaveProperty('authc.oidc.realm', 'realm-1');
});

it(`returns a validation error when authProviders is "['oidc', 'basic']" and realm is unspecified`, async () => {
const schema = await getConfigSchema(security);
const validationResult = schema.validate({
authProviders: ['oidc', 'basic'],
});
expect(validationResult.error).toMatchSnapshot();
});

it(`is valid when authProviders is "['oidc', 'basic']" and realm is specified`, async () => {
const schema = await getConfigSchema(security);
const validationResult = schema.validate({
authProviders: ['oidc', 'basic'],
authc: {
oidc: {
realm: 'realm-1',
},
},
});
expect(validationResult.error).toBeNull();
expect(validationResult.value).toHaveProperty('authc.oidc.realm', 'realm-1');
});

it(`realm is not allowed when authProviders is "['basic']"`, async () => {
const schema = await getConfigSchema(security);
const validationResult = schema.validate({
authProviders: ['basic'],
authc: {
oidc: {
realm: 'realm-1',
},
},
});
expect(validationResult.error).toMatchSnapshot();
});
});
});
});
});
52 changes: 42 additions & 10 deletions x-pack/plugins/security/server/lib/authentication/authenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
BasicAuthenticationProvider,
SAMLAuthenticationProvider,
TokenAuthenticationProvider,
OIDCAuthenticationProvider,
} from './providers';
import { AuthenticationResult } from './authentication_result';
import { DeauthenticationResult } from './deauthentication_result';
import { Session } from './session';
import { LoginAttempt } from './login_attempt';
import { AuthenticationProviderSpecificOptions } from './providers/base';

interface ProviderSession {
provider: string;
Expand All @@ -29,11 +31,15 @@ interface ProviderSession {
// provider class that can handle specific authentication mechanism.
const providerMap = new Map<
string,
new (options: AuthenticationProviderOptions) => BaseAuthenticationProvider
new (
options: AuthenticationProviderOptions,
providerSpecificOptions: AuthenticationProviderSpecificOptions
) => BaseAuthenticationProvider
>([
['basic', BasicAuthenticationProvider],
['saml', SAMLAuthenticationProvider],
['token', TokenAuthenticationProvider],
['oidc', OIDCAuthenticationProvider],
]);

function assertRequest(request: Legacy.Request) {
Expand Down Expand Up @@ -62,18 +68,44 @@ function getProviderOptions(server: Legacy.Server) {
};
}

/**
* Prepares options object that is specific only to an authentication provider.
* @param server Server instance.
* @param providerType the type of the provider to get the options for.
*/
function getProviderSpecificOptions(
server: Legacy.Server,
providerType: string
): AuthenticationProviderSpecificOptions {
const config = server.config();
// we can't use `config.has` here as it doesn't currently work with Joi's "alternatives" syntax which we
// are using to make the provider specific configuration required when the auth provider is specified
const authc = config.get<Record<string, AuthenticationProviderSpecificOptions | undefined>>(
`xpack.security.authc`
);
if (authc && authc[providerType] !== undefined) {
return authc[providerType] as AuthenticationProviderSpecificOptions;
}

return {};
}

/**
* Instantiates authentication provider based on the provider key from config.
* @param providerType Provider type key.
* @param options Options to pass to provider's constructor.
*/
function instantiateProvider(providerType: string, options: AuthenticationProviderOptions) {
function instantiateProvider(
providerType: string,
options: AuthenticationProviderOptions,
providerSpecificOptions: AuthenticationProviderSpecificOptions
) {
const ProviderClassName = providerMap.get(providerType);
if (!ProviderClassName) {
throw new Error(`Unsupported authentication provider name: ${providerType}.`);
}

return new ProviderClassName(options);
return new ProviderClassName(options, providerSpecificOptions);
}

/**
Expand Down Expand Up @@ -117,13 +149,13 @@ class Authenticator {
const providerOptions = Object.freeze(getProviderOptions(server));

this.providers = new Map(
authProviders.map(
providerType =>
[providerType, instantiateProvider(providerType, providerOptions)] as [
string,
BaseAuthenticationProvider
]
)
authProviders.map(providerType => {
const providerSpecificOptions = getProviderSpecificOptions(server, providerType);
return [
providerType,
instantiateProvider(providerType, providerOptions, providerSpecificOptions),
] as [string, BaseAuthenticationProvider];
})
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export interface AuthenticationProviderOptions {
log: (tags: string[], message: string) => void;
}

/**
* Represents available provider specific options.
*/
export type AuthenticationProviderSpecificOptions = Record<string, unknown>;

/**
* Base class that all authentication providers should extend.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { BaseAuthenticationProvider, AuthenticationProviderOptions } from './bas
export { BasicAuthenticationProvider, BasicCredentials } from './basic';
export { SAMLAuthenticationProvider } from './saml';
export { TokenAuthenticationProvider } from './token';
export { OIDCAuthenticationProvider } from './oidc';
Loading