diff --git a/packages/hawtio/src/core/core.ts b/packages/hawtio/src/core/core.ts
index 74c9d4ab..9de41ac5 100644
--- a/packages/hawtio/src/core/core.ts
+++ b/packages/hawtio/src/core/core.ts
@@ -271,6 +271,9 @@ class HawtioCore {
resolved.push(plugin)
}
}
+
+ log.debug('Resolved plugins:', resolved)
+
return resolved
}
}
diff --git a/packages/hawtio/src/plugins/connect/Connect.tsx b/packages/hawtio/src/plugins/connect/Connect.tsx
index b192763d..abdb88b7 100644
--- a/packages/hawtio/src/plugins/connect/Connect.tsx
+++ b/packages/hawtio/src/plugins/connect/Connect.tsx
@@ -16,7 +16,6 @@ import {
Modal,
ModalVariant,
PageSection,
- PageSectionVariants,
Text,
TextContent,
Toolbar,
@@ -30,78 +29,89 @@ import { ConnectModal } from './ConnectModal'
import { DELETE } from './connections'
import { ConnectContext, useConnections } from './context'
import { log } from './globals'
+import { Route, Routes } from 'react-router-dom'
+import { ConnectLogin } from './login/ConnectLogin'
export const Connect: React.FunctionComponent = () => {
const { connections, dispatch } = useConnections()
- log.debug('Connections:', connections)
-
- const ConnectHint = () => (
-
- Hint
-
- }
- >
-
- This page allows you to connect to remote processes which{' '}
-
- already have a{' '}
-
- Jolokia agent
- {' '}
- running inside them
-
- . You will need to know the host name, port and path of the Jolokia agent to be able to connect.
-
-
- If the process you wish to connect to does not have a Jolokia agent inside, please refer to the{' '}
-
- Jolokia documentation
- {' '}
- for how to add a JVM, servlet or OSGi based agent inside it.
-
-
- If you are using{' '}
-
- Red Hat Fuse{' '}
-
- or{' '}
-
- Apache ActiveMQ
-
- , then a Jolokia agent is included by default (use context path of Jolokia agent, usually
- jolokia
). Or you can always just deploy hawtio inside the process (which includes the Jolokia
- agent, use Jolokia servlet mapping inside hawtio context path, usually hawtio/jolokia
).
-
-
- )
-
- const ConnectionList = () => (
-
- )
return (
-
+
-
-
-
+
+
+ } />
+ } />
+
)
}
+const ConnectHint: React.FunctionComponent = () => (
+
+ Hint
+
+ }
+ >
+
+ This page allows you to connect to remote processes which{' '}
+
+ already have a{' '}
+
+ Jolokia agent
+ {' '}
+ running inside them
+
+ . You will need to know the host name, port and path of the Jolokia agent to be able to connect.
+
+
+ If the process you wish to connect to does not have a Jolokia agent inside, please refer to the{' '}
+
+ Jolokia documentation
+ {' '}
+ for how to add a JVM, servlet or OSGi based agent inside it.
+
+
+ If you are using{' '}
+
+ Red Hat Fuse{' '}
+
+ or{' '}
+
+ Apache ActiveMQ
+
+ , then a Jolokia agent is included by default (use context path of Jolokia agent, usually
+ jolokia
). Or you can always just deploy hawtio inside the process (which includes the Jolokia agent,
+ use Jolokia servlet mapping inside hawtio context path, usually hawtio/jolokia
).
+
+
+)
+
+const ConnectContent: React.FunctionComponent = () => {
+ const { connections } = useContext(ConnectContext)
+ log.debug('Connections:', connections)
+
+ return (
+
+
+
+
+ )
+}
+
const ConnectToolbar: React.FunctionComponent = () => {
const { connections } = useContext(ConnectContext)
const [isAddOpen, setIsAddOpen] = useState(false)
@@ -198,7 +208,7 @@ const ConnectionItem: React.FunctionComponent = props => {
return
}
- log.debug('Collecting:', connection)
+ log.debug('Connecting:', connection)
connectService.connect(connection)
}
diff --git a/packages/hawtio/src/plugins/connect/init.ts b/packages/hawtio/src/plugins/connect/init.ts
index fb6ca483..e6e79509 100644
--- a/packages/hawtio/src/plugins/connect/init.ts
+++ b/packages/hawtio/src/plugins/connect/init.ts
@@ -7,7 +7,9 @@ export async function isActive(): Promise {
return false
}
- return connectService.getCurrentConnectionName() === null
+ // The connect login path is exceptionally allowlisted to provide login form for
+ // remote Jolokia endpoints requiring authentication.
+ return connectService.getCurrentConnectionName() === null || isConnectLogin()
}
async function isProxyEnabled(): Promise {
@@ -30,3 +32,8 @@ async function isProxyEnabled(): Promise {
return true
}
}
+
+function isConnectLogin(): boolean {
+ const url = new URL(window.location.href)
+ return url.pathname === connectService.getLoginPath()
+}
diff --git a/packages/hawtio/src/plugins/connect/login/ConnectLogin.tsx b/packages/hawtio/src/plugins/connect/login/ConnectLogin.tsx
new file mode 100644
index 00000000..3f9c2f6c
--- /dev/null
+++ b/packages/hawtio/src/plugins/connect/login/ConnectLogin.tsx
@@ -0,0 +1,75 @@
+import { connectService } from '@hawtiosrc/plugins/shared'
+import { Alert, Button, Form, FormAlert, FormGroup, Modal, TextInput } from '@patternfly/react-core'
+import React, { useState } from 'react'
+
+export const ConnectLogin: React.FunctionComponent = () => {
+ const [isOpen, setIsOpen] = useState(true)
+ const [username, setUsername] = useState('')
+ const [password, setPassword] = useState('')
+ const [loginFailed, setLoginFailed] = useState(false)
+
+ const connectionName = connectService.getCurrentConnectionName()
+ if (!connectionName) {
+ return null
+ }
+
+ const handleLogin = () => {
+ const login = async () => {
+ const ok = await connectService.login(username, password)
+ if (ok) {
+ setLoginFailed(false)
+ // Redirect to the original URL
+ connectService.redirect()
+ } else {
+ setLoginFailed(true)
+ }
+ }
+ login()
+ }
+
+ const handleClose = () => {
+ setIsOpen(false)
+ }
+
+ const actions = [
+ ,
+ ,
+ ]
+
+ const title = `Log in to ${connectionName}`
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/hawtio/src/plugins/shared/__mocks__/connect-service.ts b/packages/hawtio/src/plugins/shared/__mocks__/connect-service.ts
index 5d3c8e47..2866c6bd 100644
--- a/packages/hawtio/src/plugins/shared/__mocks__/connect-service.ts
+++ b/packages/hawtio/src/plugins/shared/__mocks__/connect-service.ts
@@ -42,6 +42,14 @@ class MockConnectService implements IConnectService {
// no-op
}
+ async login(username: string, password: string): Promise {
+ return false
+ }
+
+ redirect() {
+ // no-op
+ }
+
getJolokiaUrl(connection: Connection): string {
return ''
}
@@ -50,6 +58,10 @@ class MockConnectService implements IConnectService {
return null
}
+ getLoginPath(): string {
+ return ''
+ }
+
export(connections: Connections) {
// no-op
}
diff --git a/packages/hawtio/src/plugins/shared/connect-service.ts b/packages/hawtio/src/plugins/shared/connect-service.ts
index e291bc4f..fd2a5224 100644
--- a/packages/hawtio/src/plugins/shared/connect-service.ts
+++ b/packages/hawtio/src/plugins/shared/connect-service.ts
@@ -1,4 +1,4 @@
-import { hawtio } from '@hawtiosrc/core'
+import { eventService, hawtio } from '@hawtiosrc/core'
import { toString } from '@hawtiosrc/util/strings'
import { joinPaths } from '@hawtiosrc/util/urls'
import Jolokia from 'jolokia.js'
@@ -27,10 +27,19 @@ export type ConnectionTestResult = {
message: string
}
+type ConnectionCredentials = {
+ username: string
+ password: string
+}
+
const STORAGE_KEY_CONNECTIONS = 'connect.connections'
const SESSION_KEY_CURRENT_CONNECTION = 'connect.currentConnection'
+const SESSION_KEY_CREDENTIALS = 'connect.credentials'
export const PARAM_KEY_CONNECTION = 'con'
+export const PARAM_KEY_REDIRECT = 'redirect'
+
+const LOGIN_PATH = '/connect/login'
export interface IConnectService {
getCurrentConnectionName(): string | null
@@ -42,8 +51,11 @@ export interface IConnectService {
checkReachable(connection: Connection): Promise
testConnection(connection: Connection): Promise
connect(connection: Connection): void
+ login(username: string, password: string): Promise
+ redirect(): void
getJolokiaUrl(connection: Connection): string
getJolokiaUrlFromName(name: string): string | null
+ getLoginPath(): string
export(connections: Connections): void
}
@@ -75,7 +87,26 @@ class ConnectService implements IConnectService {
}
getCurrentConnection(): Connection | null {
- return this.currentConnection ? this.getConnection(this.currentConnection) : null
+ const conn = this.currentConnection ? this.getConnection(this.currentConnection) : null
+ if (!conn) {
+ return null
+ }
+
+ // Apply credentials if it exists
+ const item = sessionStorage.getItem(SESSION_KEY_CREDENTIALS)
+ if (!item) {
+ return conn
+ }
+ const credentials = JSON.parse(item) as ConnectionCredentials
+ conn.username = credentials.username
+ conn.password = credentials.password
+ this.clearCredentialsOnLogout()
+
+ return conn
+ }
+
+ private clearCredentialsOnLogout() {
+ eventService.onLogout(() => sessionStorage.removeItem(SESSION_KEY_CREDENTIALS))
}
loadConnections(): Connections {
@@ -141,14 +172,67 @@ class ConnectService implements IConnectService {
})
}
+ private forbiddenReasonMatches(response: JQueryXHR, reason: string): boolean {
+ // Preserve compatibility with versions of Hawtio 2.x that return JSON on 403 responses
+ if (response.responseJSON && response.responseJSON['reason']) {
+ return response.responseJSON['reason'] === reason
+ }
+ // Otherwise expect a response header containing a forbidden reason
+ return response.getResponseHeader('Hawtio-Forbidden-Reason') === reason
+ }
+
connect(connection: Connection) {
log.debug('Connecting with options:', toString(connection))
- const basepath = hawtio.getBasePath() ?? '/'
- const url = `${basepath}?${PARAM_KEY_CONNECTION}=${connection.name}`
+ const basepath = hawtio.getBasePath() ?? ''
+ const url = `${basepath}/?${PARAM_KEY_CONNECTION}=${connection.name}`
log.debug('Opening URL:', url)
window.open(url)
}
+ /**
+ * Log in to the current connection.
+ */
+ async login(username: string, password: string): Promise {
+ const connection = this.getCurrentConnection()
+ if (!connection) {
+ return false
+ }
+
+ // Check credentials
+ const ok = await new Promise(resolve => {
+ connection.username = username
+ connection.password = password
+ this.createJolokia(connection, true).request(
+ { type: 'version' },
+ {
+ success: () => resolve(true),
+ error: () => resolve(false),
+ ajaxError: () => resolve(false),
+ },
+ )
+ })
+ if (!ok) {
+ return false
+ }
+
+ // Persist credentials to session storage
+ const credentials: ConnectionCredentials = { username, password }
+ sessionStorage.setItem(SESSION_KEY_CREDENTIALS, JSON.stringify(credentials))
+ this.clearCredentialsOnLogout()
+
+ return true
+ }
+
+ /**
+ * Redirect to the URL specified in the query parameter {@link PARAM_KEY_REDIRECT}.
+ */
+ redirect() {
+ const url = new URL(window.location.href)
+ const redirect = url.searchParams.get(PARAM_KEY_REDIRECT) ?? hawtio.getBasePath() ?? '/'
+ log.debug('Redirect to:', redirect)
+ window.location.href = redirect
+ }
+
/**
* Create a Jolokia instance with the given connection.
*/
@@ -200,13 +284,9 @@ class ConnectService implements IConnectService {
return connection ? this.getJolokiaUrl(connection) : null
}
- private forbiddenReasonMatches(response: JQueryXHR, reason: string): boolean {
- // Preserve compatibility with versions of Hawtio 2.x that return JSON on 403 responses
- if (response.responseJSON && response.responseJSON['reason']) {
- return response.responseJSON['reason'] === reason
- }
- // Otherwise expect a response header containing a forbidden reason
- return response.getResponseHeader('Hawtio-Forbidden-Reason') === reason
+ getLoginPath(): string {
+ const basePath = hawtio.getBasePath()
+ return `${basePath}${LOGIN_PATH}`
}
export(connections: Connections) {
diff --git a/packages/hawtio/src/plugins/shared/jolokia-service.ts b/packages/hawtio/src/plugins/shared/jolokia-service.ts
index f9e4cfb1..d36631da 100644
--- a/packages/hawtio/src/plugins/shared/jolokia-service.ts
+++ b/packages/hawtio/src/plugins/shared/jolokia-service.ts
@@ -30,7 +30,7 @@ import Jolokia, {
import 'jolokia.js/simple'
import $ from 'jquery'
import { func, is, object } from 'superstruct'
-import { PARAM_KEY_CONNECTION, connectService } from '../shared/connect-service'
+import { PARAM_KEY_CONNECTION, PARAM_KEY_REDIRECT, connectService } from '../shared/connect-service'
import { log } from './globals'
export const DEFAULT_MAX_DEPTH = 7
@@ -301,14 +301,13 @@ class JolokiaService implements IJolokiaService {
const url = new URL(window.location.href)
// If window was opened to connect to remote Jolokia endpoint
if (url.searchParams.has(PARAM_KEY_CONNECTION)) {
- const basePath = hawtio.getBasePath()
- const loginPath = `${basePath}/connect/login`
+ const loginPath = connectService.getLoginPath()
if (url.pathname !== loginPath) {
// ... and not showing the login modal
this.jolokia?.then(jolokia => jolokia.stop())
const redirectUrl = window.location.href
url.pathname = loginPath
- url.searchParams.append('redirect', redirectUrl)
+ url.searchParams.append(PARAM_KEY_REDIRECT, redirectUrl)
window.location.href = url.href
}
} else {