Skip to content

Commit a41486a

Browse files
Added origin configuration to authc providers. Changed login form to filter available providers based on the origin configuration and the current browser window origin. Also filtered available providers based on the origin header and the configured provider origin properties
1 parent 4c4b016 commit a41486a

File tree

7 files changed

+82
-11
lines changed

7 files changed

+82
-11
lines changed

docs/reference/configuration-reference/security-settings.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ xpack.security.authc.providers.<provider-type>.<provider-name>.hint ![logo cloud
7474
xpack.security.authc.providers.<provider-type>.<provider-name>.icon ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
7575
: Custom icon for the provider entry displayed on the Login Selector UI.
7676

77+
xpack.security.authc.providers.<provider-type>.<provider-name>.origin ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
78+
: TODO
79+
7780
xpack.security.authc.providers.<provider-type>.<provider-name>.showInSelector ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
7881
: Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn’t remove the provider from the authentication chain.
7982

x-pack/platform/plugins/shared/security/common/login_state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface LoginSelectorProvider {
1515
description?: string;
1616
hint?: string;
1717
icon?: string;
18+
origin?: string | string[];
1819
}
1920

2021
export interface LoginSelector {

x-pack/platform/plugins/shared/security/public/authentication/login/components/login_form/login_form.tsx

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ const assistanceCss = (theme: UseEuiTheme) => css`
133133
export class LoginForm extends Component<LoginFormProps, State> {
134134
private readonly validator: LoginValidator;
135135

136+
/**
137+
* Available providers that match the current origin.
138+
*/
139+
private readonly availableProviders: LoginSelectorProvider[];
140+
136141
/**
137142
* Optional provider that was suggested by the `auth_provider_hint={providerName}` query string parameter. If provider
138143
* doesn't require Kibana native login form then login process is triggered automatically, otherwise Login Selector
@@ -142,10 +147,15 @@ export class LoginForm extends Component<LoginFormProps, State> {
142147

143148
constructor(props: LoginFormProps) {
144149
super(props);
150+
151+
this.availableProviders = this.props.selector.providers.filter((provider) =>
152+
this.providerMatchesOrigin(provider)
153+
);
154+
145155
this.validator = new LoginValidator({ shouldValidate: false });
146156

147157
this.suggestedProvider = this.props.authProviderHint
148-
? this.props.selector.providers.find(({ name }) => name === this.props.authProviderHint)
158+
? this.availableProviders.find(({ name }) => name === this.props.authProviderHint)
149159
: undefined;
150160

151161
// Switch to the Form mode right away if provider from the hint requires it.
@@ -158,7 +168,14 @@ export class LoginForm extends Component<LoginFormProps, State> {
158168
loadingState: { type: LoadingStateType.None },
159169
username: '',
160170
password: '',
161-
message: this.props.message || { type: MessageType.None },
171+
message:
172+
this.props.message ??
173+
(this.availableProviders.length === 0
174+
? {
175+
type: MessageType.Danger,
176+
content: 'No authentication providers have been configured for this domain.',
177+
}
178+
: { type: MessageType.None }),
162179
mode,
163180
previousMode: mode,
164181
};
@@ -236,6 +253,10 @@ export class LoginForm extends Component<LoginFormProps, State> {
236253
};
237254

238255
public renderContent() {
256+
if (this.availableProviders.length === 0) {
257+
return;
258+
}
259+
239260
switch (this.state.mode) {
240261
case PageMode.Form:
241262
return this.renderLoginForm();
@@ -339,7 +360,8 @@ export class LoginForm extends Component<LoginFormProps, State> {
339360
};
340361

341362
private renderSelector = () => {
342-
const providers = this.props.selector.providers.filter((p) => p.showInSelector);
363+
const providers = this.availableProviders.filter((p) => p.showInSelector);
364+
343365
return (
344366
<EuiPanel data-test-subj="loginSelector" paddingSize="none">
345367
{providers.map((provider) => {
@@ -513,9 +535,7 @@ export class LoginForm extends Component<LoginFormProps, State> {
513535
});
514536

515537
// We try to log in with the provider that uses login form and has the lowest order.
516-
const providerToLoginWith = this.props.selector.providers.find(
517-
(provider) => provider.usesLoginForm
518-
)!;
538+
const providerToLoginWith = this.availableProviders.find((provider) => provider.usesLoginForm)!;
519539

520540
try {
521541
const { location } = await this.props.http.post<{ location: string }>(
@@ -603,9 +623,17 @@ export class LoginForm extends Component<LoginFormProps, State> {
603623
private showLoginSelector() {
604624
return (
605625
this.props.selector.enabled &&
606-
this.props.selector.providers.some(
607-
(provider) => !provider.usesLoginForm && provider.showInSelector
608-
)
626+
this.availableProviders.some((provider) => !provider.usesLoginForm && provider.showInSelector)
627+
);
628+
}
629+
630+
private providerMatchesOrigin(provider: LoginSelectorProvider): boolean {
631+
const { origin } = window.location;
632+
return (
633+
!provider.origin ||
634+
(Array.isArray(provider.origin)
635+
? provider.origin.includes(origin)
636+
: provider.origin === origin)
609637
);
610638
}
611639
}

x-pack/platform/plugins/shared/security/server/authentication/authenticator.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export class Authenticator {
273273
name,
274274
logger: options.loggers.get(type, name),
275275
urls: { loggedOut: (request: KibanaRequest) => this.getLoggedOutURL(request, type) },
276+
origin: this.options.config.authc.providers[type]?.[name].origin,
276277
}),
277278
this.options.config.authc.providers[type]?.[name]
278279
),
@@ -336,7 +337,32 @@ export class Authenticator {
336337
return AuthenticationResult.notHandled();
337338
}
338339

339-
for (const [providerName, provider] of providers) {
340+
const { origin: originHeader } = request.headers;
341+
342+
const filteredProviders = providers.filter(([name, provider]) => {
343+
const providerOrigin = provider.getOrigin();
344+
345+
return (
346+
!originHeader ||
347+
!providerOrigin ||
348+
(Array.isArray(providerOrigin)
349+
? providerOrigin.includes(originHeader as string)
350+
: providerOrigin === originHeader)
351+
);
352+
});
353+
354+
if (filteredProviders.length === 0) {
355+
this.logger.warn(
356+
`Login attempt for provider with ${
357+
isLoginAttemptWithProviderName(attempt)
358+
? `name ${attempt.provider.name}`
359+
: `type "${(attempt.provider as Record<string, string>).type}"`
360+
} is detected, but originated from an invalid origin.`
361+
);
362+
return AuthenticationResult.notHandled();
363+
}
364+
365+
for (const [providerName, provider] of filteredProviders) {
340366
// Check if current session has been set by this provider.
341367
const ownsSession =
342368
existingSessionValue?.provider.name === providerName &&

x-pack/platform/plugins/shared/security/server/authentication/providers/base.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface AuthenticationProviderOptions {
4141
loggedOut: (request: KibanaRequest) => string;
4242
};
4343
isElasticCloudDeployment: () => boolean;
44+
origin?: string | string[];
4445
}
4546

4647
/**
@@ -147,4 +148,11 @@ export abstract class BaseAuthenticationProvider {
147148
authenticationInfo.authentication_realm.name === ELASTIC_CLOUD_SSO_REALM_NAME,
148149
} as AuthenticatedUser);
149150
}
151+
152+
/**
153+
* Returns the origin option associated with the provider.
154+
*/
155+
getOrigin(): string | string[] | undefined {
156+
return this.options.origin;
157+
}
150158
}

x-pack/platform/plugins/shared/security/server/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface ProvidersCommonConfigType {
2828
description?: Type<string>;
2929
hint?: Type<string>;
3030
icon?: Type<string>;
31+
origin?: Type<string[] | string>;
3132
session?: Type<{ idleTimeout?: Duration | null; lifespan?: Duration | null }>;
3233
}
3334

@@ -49,6 +50,7 @@ function getCommonProviderSchemaProperties(overrides: Partial<ProvidersCommonCon
4950
description: schema.maybe(schema.string()),
5051
hint: schema.maybe(schema.string()),
5152
icon: schema.maybe(schema.string()),
53+
origin: schema.maybe(schema.oneOf([schema.uri(), schema.arrayOf(schema.uri())])),
5254
accessAgreement: schema.maybe(schema.object({ message: schema.string() })),
5355
session: schema.object({
5456
idleTimeout: schema.maybe(schema.oneOf([schema.duration(), schema.literal(null)])),

x-pack/platform/plugins/shared/security/server/routes/views/login.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ export function defineLoginRoutes({
8585
const providers = sortedProviders.map(({ type, name }) => {
8686
// Since `config.authc.sortedProviders` is based on `config.authc.providers` config we can
8787
// be sure that config is present for every provider in `config.authc.sortedProviders`.
88-
const { showInSelector, description, hint, icon } = config.authc.providers[type]?.[name]!;
88+
89+
const { showInSelector, description, hint, icon, origin } =
90+
config.authc.providers[type]?.[name]!;
8991
const usesLoginForm = shouldProviderUseLoginForm(type);
9092
return {
9193
type,
@@ -95,6 +97,7 @@ export function defineLoginRoutes({
9597
description,
9698
hint,
9799
icon,
100+
origin,
98101
};
99102
});
100103

0 commit comments

Comments
 (0)