Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions src/shared/modules/app/appDuck.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Action type constants
export const NAME = 'app'
export const APP_START = `${NAME}/APP_START`
export const USER_CLEAR = `${NAME}/USER_CLEAR`

// Selectors
export const getHostedUrl = (state) => (state[NAME] || {}).hostedUrl || null

// Reducer
export default function reducer (state = { hostedUrl: null }, action) {
switch (action.type) {
case APP_START:
return {...state, hostedUrl: action.url}
default:
return state
}
}
51 changes: 51 additions & 0 deletions src/shared/modules/app/appDuck.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/* global test, expect */
import reducer, { NAME, APP_START, getHostedUrl } from './appDuck'

test('reducer stores hostedUrl', () => {
// Given
const url = 'xxx'
const initState = {}
const action = { type: APP_START, url }

// When
const state = reducer(initState, action)

// Then
expect(state.hostedUrl).toEqual(url)
})

test('selector getHostedUrl returns whats in the store', () => {
// Given
const url = 'xxx'
const initState = {}
const action = { type: APP_START, url }

// Then
expect(getHostedUrl({[NAME]: initState})).toEqual(null)

// When
const state = reducer(initState, action)

// Then
expect(getHostedUrl({[NAME]: state})).toEqual(url)
})
4 changes: 3 additions & 1 deletion src/shared/rootReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { syncReducer, syncConsentReducer, NAME_CONSENT as syncConsent, NAME as s
import foldersReducer, { NAME as folders } from 'shared/modules/favorites/foldersDuck'
import commandsReducer, { NAME as commands } from 'shared/modules/commands/commandsDuck'
import udcReducer, { NAME as udc } from 'shared/modules/udc/udcDuck'
import appReducer, { NAME as app } from 'shared/modules/app/appDuck'

export default {
[connections]: connectionsReducer,
Expand All @@ -52,5 +53,6 @@ export default {
[sync]: syncReducer,
[syncConsent]: syncConsentReducer,
[commands]: commandsReducer,
[udc]: udcReducer
[udc]: udcReducer,
[app]: appReducer
}
17 changes: 15 additions & 2 deletions src/shared/services/commandInterpreterHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
*/

import * as frames from 'shared/modules/stream/streamDuck'
import { getHostedUrl } from 'shared/modules/app/appDuck'
import { getHistory } from 'shared/modules/history/historyDuck'
import { update as updateQueryResult } from 'shared/modules/requests/requestsDuck'
import { getActiveConnectionData } from 'shared/modules/connections/connectionsDuck'
import { getParams } from 'shared/modules/params/paramsDuck'
import { updateGraphStyleData } from 'shared/modules/grass/grassDuck'
import { getRemoteContentHostnameWhitelist } from 'shared/modules/dbMeta/dbMetaDuck'
import { fetchRemoteGuide } from 'shared/modules/commands/helpers/play'
import remote from 'services/remote'
import { isLocalRequest, authHeaderFromCredentials } from 'services/remoteUtils'
import { handleServerCommand } from 'shared/modules/commands/helpers/server'
import { handleCypherCommand } from 'shared/modules/commands/helpers/cypher'
import { unknownCommand, showErrorMessage, cypher, successfulCypher, unsuccessfulCypher } from 'shared/modules/commands/commandsDuck'
Expand Down Expand Up @@ -156,10 +159,20 @@ const availableCommands = [{
}, {
name: 'http',
match: (cmd) => /^(get|post|put|delete|head)/i.test(cmd),
exec: (action, cmdchar, put) => {
exec: (action, cmdchar, put, store) => {
return parseHttpVerbCommand(action.cmd)
.then((r) => {
remote.request(r.method, r.url, r.data)
const hostedUrl = getHostedUrl(store.getState())
const isLocal = isLocalRequest(hostedUrl, r.url, { hostnameOnly: false })
const connectionData = getActiveConnectionData(store.getState())
const isSameHostnameAsConnection = isLocalRequest(connectionData.host, r.url, { hostnameOnly: true })
let authHeaders = {}
if (isLocal || isSameHostnameAsConnection) {
if (connectionData.username) {
authHeaders = { 'Authorization': 'Basic ' + authHeaderFromCredentials(connectionData.username, connectionData.password) }
}
}
remote.request(r.method, r.url, r.data, authHeaders)
.then((res) => res.text())
.then((res) => {
put(frames.add({...action, result: res, type: 'pre'}))
Expand Down
14 changes: 8 additions & 6 deletions src/shared/services/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@
/* global fetch */
import 'isomorphic-fetch'

function request (method, url, data = null) {
function request (method, url, data = null, extraHeaders = {}) {
const headers = {
'Content-Type': 'application/json',
'X-Ajax-Browser-Auth': 'true',
'X-stream': 'true',
...extraHeaders
}
return fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-Ajax-Browser-Auth': 'true',
'X-stream': 'true'
},
headers: headers,
body: data
})
.then(checkStatus)
Expand Down
25 changes: 25 additions & 0 deletions src/shared/services/remoteUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/* global btoa */
import { getUrlInfo } from 'services/utils'

export function cleanHtml (string) {
if (typeof string !== 'string') return string
string = string.replace(/(\s+(on[^\s=]+)[^\s=]*\s*=\s*("[^"]*"|'[^']*'|[\w\-.:]+\s*))/ig, '')
return string.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*(<\/script>)?/gi, '')
}

export const authHeaderFromCredentials = (username, password) => {
if (!btoa) throw new Error('btoa not defined') // Non browser env
return btoa(`${username}:${password}`)
}

export const isLocalRequest = (localUrl, requestUrl, opts = { hostnameOnly: false }) => {
if (!localUrl) return false

if (opts.hostnameOnly === true) {
localUrl = localUrl.trim().replace(/^[^:]+:\/\//, '')
requestUrl = requestUrl.trim().replace(/^[^:]+:\/\//, '')
}
const localUrlInfo = getUrlInfo(localUrl)
const requestUrlInfo = getUrlInfo(requestUrl)
if (!requestUrlInfo.host) return true // GET /path
if (opts.hostnameOnly === true) return requestUrlInfo.hostname === localUrlInfo.hostname // GET localhost:8080 from localhost:9000
if (requestUrlInfo.host === localUrlInfo.host && requestUrlInfo.protocol === localUrlInfo.protocol) { // Same host and protocol
return true
}
return false
}
32 changes: 30 additions & 2 deletions src/shared/services/remoteUtils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,40 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/* global test, expect */
/* global describe, test, expect */
import * as utils from './remoteUtils'

describe('commandutils', () => {
describe('remoteUtils', () => {
test('removes script tags', () => {
const text = 'hello<script>alert(1)</script> <p onclick="alert(1)">test</p>'
expect(utils.cleanHtml(text)).toEqual('hello <p>test</p>')
})
test('isLocalRequest figures out if a request is local or remote', () => {
// Given
const itemsStrict = [
{ local: undefined, request: '/yo', expect: false },
{ local: 'http://hej.com', request: '/yo', expect: true },
{ local: 'http://hej.com', request: 'http://hej.com/yo', expect: true },
{ local: 'http://hej.com:8080', request: 'http://hej.com:9000/mine', expect: false },
{ local: 'http://hej.com', request: 'https://hej.com', expect: false },
{ local: 'http://hej.com', request: 'http://bye.com', expect: false }
]
const itemsHostnameOnly = [
{ local: undefined, request: '/yo', expect: false },
{ local: 'http://hej.com', request: '/yo', expect: true },
{ local: 'http://hej.com', request: 'http://hej.com/yo', expect: true },
{ local: 'http://hej.com:8080', request: 'http://hej.com:9000/mine', expect: true },
{ local: 'http://hej.com', request: 'https://hej.com', expect: true },
{ local: 'http://hej.com', request: 'http://bye.com', expect: false },
{ local: 'bolt://hej.com:7687', request: 'http://hej.com:7474', expect: true }
]

// When && Then
itemsStrict.forEach((item) => {
expect(utils.isLocalRequest(item.local, item.request)).toBe(item.expect)
})
itemsHostnameOnly.forEach((item) => {
expect(utils.isLocalRequest(item.local, item.request, { hostnameOnly: true })).toBe(item.expect)
})
})
})