Skip to content

Commit

Permalink
Expose session invalidation API. (elastic#92376) (elastic#95274)
Browse files Browse the repository at this point in the history
Co-authored-by: Aleh Zasypkin <[email protected]>
  • Loading branch information
kibanamachine and azasypkin authored Mar 24, 2021
1 parent b0db75b commit 84e9a4c
Show file tree
Hide file tree
Showing 20 changed files with 1,163 additions and 93 deletions.
9 changes: 9 additions & 0 deletions docs/api/session-management.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[role="xpack"]
[[session-management-api]]
== User session management APIs

The following <<xpack-security-session-management, user session>> management APIs are available:

* <<session-management-api-invalidate, Invalidate user sessions API>> to invalidate user sessions

include::session-management/invalidate.asciidoc[]
114 changes: 114 additions & 0 deletions docs/api/session-management/invalidate.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
[[session-management-api-invalidate]]
=== Invalidate user sessions API
++++
<titleabbrev>Invalidate user sessions</titleabbrev>
++++

experimental[] Invalidates user sessions that match provided query.

[[session-management-api-invalidate-prereqs]]
==== Prerequisite

To use the invalidate user sessions API, you must be a `superuser`.

[[session-management-api-invalidate-request]]
==== Request

`POST <kibana host>:<port>/api/security/session/_invalidate`

[role="child_attributes"]
[[session-management-api-invalidate-request-body]]
==== Request body

`match`::
(Required, string) Specifies how {kib} determines which sessions to invalidate. Can either be `all` to invalidate all existing sessions, or `query` to only invalidate sessions that match the query specified in the additional `query` parameter.

`query`::
(Optional, object) Specifies the query that {kib} uses to match the sessions to invalidate when the `match` parameter is set to `query`. You cannot use this parameter if `match` is set to `all`.
+
.Properties of `query`
[%collapsible%open]
=====
`provider` :::
(Required, object) Describes the <<authentication-security-settings, authentication providers>> for which to invalidate sessions.
`type` ::::
(Required, string) The authentication provider `type`.
`name` ::::
(Optional, string) The authentication provider `name`.
`username` :::
(Optional, string) The username for which to invalidate sessions.
=====

[[session-management-api-invalidate-response-body]]
==== Response body

`total`::
(number) The number of successfully invalidated sessions.

[[session-management-api-invalidate-response-codes]]
==== Response codes

`200`::
Indicates a successful call.

`403`::
Indicates that the user may not be authorized to invalidate sessions for other users. Refer to <<session-management-api-invalidate-prereqs, prerequisites>>.

==== Examples

Invalidate all existing sessions:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "all"
}
--------------------------------------------------
// KIBANA

Invalidate sessions that were created by any <<saml, SAML authentication provider>>:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "saml" }
}
}
--------------------------------------------------
// KIBANA

Invalidate sessions that were created by the <<saml, SAML authentication provider>> with the name `saml1`:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "saml", "name": "saml1" }
}
}
--------------------------------------------------
// KIBANA

Invalidate sessions that were created by any <<oidc, OpenID Connect authentication provider>> for the user with the username `[email protected]`:

[source,sh]
--------------------------------------------------
$ curl -X POST api/security/session/_invalidate
{
"match" : "query",
"query": {
"provider" : { "type": "oidc" },
"username": "[email protected]"
}
}
--------------------------------------------------
// KIBANA
1 change: 1 addition & 0 deletions docs/user/api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ curl -X POST \
include::{kib-repo-dir}/api/features.asciidoc[]
include::{kib-repo-dir}/api/spaces-management.asciidoc[]
include::{kib-repo-dir}/api/role-management.asciidoc[]
include::{kib-repo-dir}/api/session-management.asciidoc[]
include::{kib-repo-dir}/api/saved-objects.asciidoc[]
include::{kib-repo-dir}/api/alerts.asciidoc[]
include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[]
Expand Down
8 changes: 8 additions & 0 deletions docs/user/security/authentication/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ To make this iframe leverage anonymous access automatically, you will need to mo

Note that `auth_provider_hint` query string parameter goes *before* the hash URL fragment.

[float]
[[anonymous-access-session]]
===== Anonymous access session

{kib} maintains a separate <<xpack-security-session-management, session>> for every anonymous user, as it does for all other authentication mechanisms.

You can configure <<session-idle-timeout, session idle timeout>> and <<session-lifespan, session lifespan>> for anonymous sessions the same as you do for any other session with the exception that idle timeout is explicitly disabled for anonymous sessions by default. The global <<security-session-and-cookie-settings, `xpack.security.session.idleTimeout`>> setting doesn't affect anonymous sessions. To change the idle timeout for anonymous sessions, you must configure the provider-level <<anonymous-authentication-provider-settings, `xpack.security.authc.providers.anonymous.<provider-name>.session.idleTimeout`>> setting.

[[http-authentication]]
==== HTTP authentication

Expand Down
2 changes: 2 additions & 0 deletions docs/user/security/session-management.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ When you log in, {kib} creates a session that is used to authenticate subsequent

When your session expires, or you log out, {kib} will invalidate your cookie and remove session information from the index. {kib} also periodically invalidates and removes any expired sessions that weren't explicitly invalidated.

To manage user sessions programmatically, {kib} exposes <<session-management-api, session management APIs>>.

[[session-idle-timeout]]
==== Session idle timeout

Expand Down
50 changes: 25 additions & 25 deletions x-pack/plugins/security/server/authentication/authenticator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,8 +586,8 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
});

it('clears session if provider asked to do so in `succeeded` result.', async () => {
Expand All @@ -606,8 +606,8 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
});

it('clears session if provider asked to do so in `redirected` result.', async () => {
Expand All @@ -625,8 +625,8 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
});

describe('with Access Agreement', () => {
Expand Down Expand Up @@ -1192,7 +1192,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('extends session for non-system API calls.', async () => {
Expand All @@ -1214,7 +1214,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.extend).toHaveBeenCalledWith(request, mockSessVal);
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('does not touch session for system API calls if authentication fails with non-401 reason.', async () => {
Expand All @@ -1235,7 +1235,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('does not touch session for non-system API calls if authentication fails with non-401 reason.', async () => {
Expand All @@ -1256,7 +1256,7 @@ describe('Authenticator', () => {
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('replaces existing session with the one returned by authentication provider for system API requests', async () => {
Expand All @@ -1282,7 +1282,7 @@ describe('Authenticator', () => {
});
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('replaces existing session with the one returned by authentication provider for non-system API requests', async () => {
Expand All @@ -1308,7 +1308,7 @@ describe('Authenticator', () => {
});
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('clears session if provider failed to authenticate system API request with 401 with active session.', async () => {
Expand All @@ -1325,8 +1325,8 @@ describe('Authenticator', () => {
AuthenticationResult.failed(Boom.unauthorized())
);

expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1346,8 +1346,8 @@ describe('Authenticator', () => {
AuthenticationResult.failed(Boom.unauthorized())
);

expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1365,8 +1365,8 @@ describe('Authenticator', () => {
AuthenticationResult.redirectTo('some-url', { state: null })
);

expect(mockOptions.session.clear).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalledWith(request);
expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' });
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1383,7 +1383,7 @@ describe('Authenticator', () => {
AuthenticationResult.notHandled()
);

expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand All @@ -1400,7 +1400,7 @@ describe('Authenticator', () => {
AuthenticationResult.notHandled()
);

expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
expect(mockOptions.session.create).not.toHaveBeenCalled();
expect(mockOptions.session.update).not.toHaveBeenCalled();
expect(mockOptions.session.extend).not.toHaveBeenCalled();
Expand Down Expand Up @@ -1790,7 +1790,7 @@ describe('Authenticator', () => {
DeauthenticationResult.notHandled()
);

expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('clears session and returns whatever authentication provider returns.', async () => {
Expand All @@ -1805,7 +1805,7 @@ describe('Authenticator', () => {
);

expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockOptions.session.clear).toHaveBeenCalled();
expect(mockOptions.session.invalidate).toHaveBeenCalled();
});

it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => {
Expand All @@ -1824,7 +1824,7 @@ describe('Authenticator', () => {

expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request, null);
expect(mockOptions.session.clear).toHaveBeenCalled();
expect(mockOptions.session.invalidate).toHaveBeenCalled();
});

it('if session does not exist and provider name is not available, returns whatever authentication provider returns.', async () => {
Expand All @@ -1841,7 +1841,7 @@ describe('Authenticator', () => {

expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request);
expect(mockOptions.session.clear).not.toHaveBeenCalled();
expect(mockOptions.session.invalidate).not.toHaveBeenCalled();
});

it('returns `notHandled` if session does not exist and provider name is invalid', async () => {
Expand All @@ -1853,7 +1853,7 @@ describe('Authenticator', () => {
);

expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled();
expect(mockOptions.session.clear).toHaveBeenCalled();
expect(mockOptions.session.invalidate).toHaveBeenCalled();
});
});

Expand Down
Loading

0 comments on commit 84e9a4c

Please sign in to comment.