-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
86 changed files
with
3,943 additions
and
2,535 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { AppMount, ScopedHistory } from 'src/core/public'; | ||
import { captureURLApp } from './capture_url_app'; | ||
|
||
import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; | ||
|
||
describe('captureURLApp', () => { | ||
beforeAll(() => { | ||
Object.defineProperty(window, 'location', { | ||
value: { href: 'https://some-host' }, | ||
writable: true, | ||
}); | ||
}); | ||
|
||
it('properly registers application', () => { | ||
const coreSetupMock = coreMock.createSetup(); | ||
|
||
captureURLApp.create(coreSetupMock); | ||
|
||
expect(coreSetupMock.http.anonymousPaths.register).toHaveBeenCalledTimes(1); | ||
expect(coreSetupMock.http.anonymousPaths.register).toHaveBeenCalledWith( | ||
'/internal/security/capture-url' | ||
); | ||
|
||
expect(coreSetupMock.application.register).toHaveBeenCalledTimes(1); | ||
|
||
const [[appRegistration]] = coreSetupMock.application.register.mock.calls; | ||
expect(appRegistration).toEqual({ | ||
id: 'security_capture_url', | ||
chromeless: true, | ||
appRoute: '/internal/security/capture-url', | ||
title: 'Capture URL', | ||
mount: expect.any(Function), | ||
}); | ||
}); | ||
|
||
it('properly handles captured URL', async () => { | ||
window.location.href = `https://host.com/mock-base-path/internal/security/capture-url?next=${encodeURIComponent( | ||
'/mock-base-path/app/home' | ||
)}&providerType=saml&providerName=saml1#/?_g=()`; | ||
|
||
const coreSetupMock = coreMock.createSetup(); | ||
coreSetupMock.http.post.mockResolvedValue({ location: '/mock-base-path/app/home#/?_g=()' }); | ||
|
||
captureURLApp.create(coreSetupMock); | ||
|
||
const [[{ mount }]] = coreSetupMock.application.register.mock.calls; | ||
await (mount as AppMount)({ | ||
element: document.createElement('div'), | ||
appBasePath: '', | ||
onAppLeave: jest.fn(), | ||
history: (scopedHistoryMock.create() as unknown) as ScopedHistory, | ||
}); | ||
|
||
expect(coreSetupMock.http.post).toHaveBeenCalledTimes(1); | ||
expect(coreSetupMock.http.post).toHaveBeenCalledWith('/internal/security/login', { | ||
body: JSON.stringify({ | ||
providerType: 'saml', | ||
providerName: 'saml1', | ||
currentURL: `https://host.com/mock-base-path/internal/security/capture-url?next=${encodeURIComponent( | ||
'/mock-base-path/app/home' | ||
)}&providerType=saml&providerName=saml1#/?_g=()`, | ||
}), | ||
}); | ||
|
||
expect(window.location.href).toBe('/mock-base-path/app/home#/?_g=()'); | ||
}); | ||
}); |
68 changes: 68 additions & 0 deletions
68
x-pack/plugins/security/public/authentication/capture_url/capture_url_app.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { parse } from 'url'; | ||
import { ApplicationSetup, FatalErrorsSetup, HttpSetup } from 'src/core/public'; | ||
|
||
interface CreateDeps { | ||
application: ApplicationSetup; | ||
http: HttpSetup; | ||
fatalErrors: FatalErrorsSetup; | ||
} | ||
|
||
/** | ||
* Some authentication providers need to know current user URL to, for example, restore it after a | ||
* complex authentication handshake. But most of the Kibana URLs include hash fragment that is never | ||
* sent to the server. To capture that authentication provider can redirect user to this app putting | ||
* path segment into the `next` query string parameter (so that it's not lost during redirect). And | ||
* since browsers preserve hash fragments during redirects (assuming redirect location doesn't | ||
* specify its own hash fragment, which is true in our case) this app can capture both path and | ||
* hash URL segments and send them back to the authentication provider via login endpoint. | ||
* | ||
* The flow can look like this: | ||
* 1. User visits `/app/kibana#/management/elasticsearch` that initiates authentication. | ||
* 2. Provider redirect user to `/internal/security/capture-url?next=%2Fapp%2Fkibana&providerType=saml&providerName=saml1`. | ||
* 3. Browser preserves hash segment and users ends up at `/internal/security/capture-url?next=%2Fapp%2Fkibana&providerType=saml&providerName=saml1#/management/elasticsearch`. | ||
* 4. The app captures full URL and sends it back as is via login endpoint: | ||
* { | ||
* providerType: 'saml', | ||
* providerName: 'saml1', | ||
* currentURL: 'https://kibana.com/internal/security/capture-url?next=%2Fapp%2Fkibana&providerType=saml&providerName=saml1#/management/elasticsearch' | ||
* } | ||
* 5. Login endpoint handler parses and validates `next` parameter, joins it with the hash segment | ||
* and finally passes it to the provider that initiated capturing. | ||
*/ | ||
export const captureURLApp = Object.freeze({ | ||
id: 'security_capture_url', | ||
create({ application, fatalErrors, http }: CreateDeps) { | ||
http.anonymousPaths.register('/internal/security/capture-url'); | ||
application.register({ | ||
id: this.id, | ||
title: 'Capture URL', | ||
chromeless: true, | ||
appRoute: '/internal/security/capture-url', | ||
async mount() { | ||
try { | ||
const { providerName, providerType } = parse(window.location.href, true).query ?? {}; | ||
if (!providerName || !providerType) { | ||
fatalErrors.add(new Error('Provider to capture URL for is not specified.')); | ||
return () => {}; | ||
} | ||
|
||
const { location } = await http.post<{ location: string }>('/internal/security/login', { | ||
body: JSON.stringify({ providerType, providerName, currentURL: window.location.href }), | ||
}); | ||
|
||
window.location.href = location; | ||
} catch (err) { | ||
fatalErrors.add(new Error('Cannot login with captured URL.')); | ||
} | ||
|
||
return () => {}; | ||
}, | ||
}); | ||
}, | ||
}); |
7 changes: 7 additions & 0 deletions
7
x-pack/plugins/security/public/authentication/capture_url/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
export { captureURLApp } from './capture_url_app'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.