Skip to content

Commit ca57ff6

Browse files
authored
Merge pull request #818 from oskarhane/allow-non-reader
Enable users with non reader roles to connect
2 parents be71927 + a2ab143 commit ca57ff6

6 files changed

Lines changed: 126 additions & 69 deletions

File tree

e2e_tests/integration/bolt.spec.js

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21-
/* global cy, test, expect */
21+
/* global Cypress, cy, test, expect */
2222

2323
describe('Bolt connections', () => {
2424
it('show "no connection" error when not using web workers', () => {
@@ -34,13 +34,50 @@ describe('Bolt connections', () => {
3434
cy.resultContains('No connection found, did you connect to Neo4j')
3535
})
3636
it('does not show the "Reconnect" banner when trying to connect', () => {
37-
cy.connect('neo4j', 'x', 'bolt://localhost:7685', false) // Non open port
37+
cy.connect(
38+
'neo4j',
39+
'x',
40+
'bolt://localhost:7685',
41+
false
42+
) // Non open port
3843
cy.wait(10000)
3944
cy.get('[data-test-id="reconnectBanner"]').should('not.be.visible')
4045
cy.get('[data-test-id="disconnectedBanner"]').should('be.visible')
41-
cy
42-
.get('[data-test-id="main"]', { timeout: 1000 })
46+
cy.get('[data-test-id="main"]', { timeout: 1000 })
4347
.and('contain', 'Database access not available')
4448
.should('not.contain', 'Connection lost')
4549
})
50+
it('users with no role can connect', () => {
51+
cy.executeCommand(':clear')
52+
const password = Cypress.env('browser-password') || 'newpassword'
53+
cy.connect(
54+
'neo4j',
55+
password
56+
)
57+
cy.executeCommand('CALL dbms.security.deleteUser("no-roles")')
58+
cy.executeCommand(':clear')
59+
cy.executeCommand('CALL dbms.security.createUser("no-roles", "pw", true)')
60+
cy.executeCommand(':server disconnect')
61+
cy.executeCommand(':clear')
62+
cy.executeCommand(':server connect')
63+
64+
// Make sure initial pw set works
65+
cy.setInitialPassword('.', 'pw', 'no-roles', true)
66+
67+
// Try regular connect
68+
cy.executeCommand(':server disconnect')
69+
cy.connect(
70+
'no-roles',
71+
'.'
72+
)
73+
74+
// We need to reset the local storage value to
75+
// default so other tests can pass
76+
cy.executeCommand(':server disconnect')
77+
cy.connect(
78+
'neo4j',
79+
password
80+
)
81+
cy.executeCommand(':server disconnect')
82+
})
4683
})

e2e_tests/support/commands.js

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,50 @@ const Editor = '.ReactCodeMirror textarea'
44

55
/* global Cypress, cy */
66

7-
Cypress.Commands.add('setInitialPassword', newPassword => {
8-
if (Cypress.env('E2E_TEST_ENV') === 'local') {
9-
// We assume pw already set on local
10-
return
11-
}
12-
cy.title().should('include', 'Neo4j Browser')
13-
14-
cy
15-
.get('input[data-test-id="boltaddress"]')
16-
.clear()
17-
.type('bolt://localhost:7687')
18-
19-
cy.get('input[data-test-id="username"]').should('have.value', 'neo4j')
20-
cy.get('input[data-test-id="password"]').should('have.value', '')
7+
Cypress.Commands.add(
8+
'setInitialPassword',
9+
(
10+
newPassword,
11+
initialPassword = 'neo4j',
12+
username = 'neo4j',
13+
force = false
14+
) => {
15+
if (Cypress.env('E2E_TEST_ENV') === 'local' && !force) {
16+
// We assume pw already set on local
17+
return
18+
}
19+
cy.title().should('include', 'Neo4j Browser')
2120

22-
cy.get('input[data-test-id="password"]').type('neo4j')
21+
cy.get('input[data-test-id="boltaddress"]')
22+
.clear()
23+
.type('bolt://localhost:7687')
2324

24-
cy.get('input[data-test-id="username"]').should('have.value', 'neo4j')
25+
cy.get('input[data-test-id="username"]')
26+
.clear()
27+
.type(username)
28+
cy.get('input[data-test-id="password"]').type(initialPassword)
2529

26-
cy.get('button[data-test-id="connect"]').click()
30+
cy.get('button[data-test-id="connect"]').click()
2731

28-
// update password
29-
cy.get('input[data-test-id="newPassword"]')
30-
cy.get('input[data-test-id="newPassword"]').should('have.value', '')
31-
cy
32-
.get('input[data-test-id="newPasswordConfirmation"]')
33-
.should('have.value', '')
32+
// update password
33+
cy.get('input[data-test-id="newPassword"]')
34+
cy.get('input[data-test-id="newPassword"]').should('have.value', '')
35+
cy.get('input[data-test-id="newPasswordConfirmation"]').should(
36+
'have.value',
37+
''
38+
)
3439

35-
cy.get('input[data-test-id="newPassword"]').type(newPassword)
36-
cy.get('input[data-test-id="newPasswordConfirmation"]').type(newPassword)
37-
cy.get('button[data-test-id="changePassword"]').click()
40+
cy.get('input[data-test-id="newPassword"]').type(newPassword)
41+
cy.get('input[data-test-id="newPasswordConfirmation"]').type(newPassword)
42+
cy.get('button[data-test-id="changePassword"]').click()
3843

39-
cy.get('input[data-test-id="changePassword"]').should('not.be.visible')
40-
cy
41-
.get('[data-test-id="frameCommand"]', { timeout: 10000 })
42-
.should('contain', ':play start')
43-
})
44+
cy.get('input[data-test-id="changePassword"]').should('not.be.visible')
45+
cy.get('[data-test-id="frameCommand"]', { timeout: 10000 }).should(
46+
'contain',
47+
':play start'
48+
)
49+
}
50+
)
4451
Cypress.Commands.add(
4552
'connect',
4653
(
@@ -53,31 +60,25 @@ Cypress.Commands.add(
5360
cy.executeCommand(':clear')
5461
cy.executeCommand(':server connect')
5562

56-
cy
57-
.get('input[data-test-id="boltaddress"]')
63+
cy.get('input[data-test-id="boltaddress"]')
5864
.clear()
5965
.type(host)
6066

61-
cy.get('input[data-test-id="username"]').should('have.value', 'neo4j')
62-
cy.get('input[data-test-id="password"]').should('have.value', '')
63-
64-
cy
65-
.get('input[data-test-id="username"]')
67+
cy.get('input[data-test-id="username"]')
6668
.clear()
6769
.type(username)
68-
cy
69-
.get('input[data-test-id="password"]')
70+
cy.get('input[data-test-id="password"]')
7071
.clear()
7172
.type(password)
7273

7374
cy.get('button[data-test-id="connect"]').click()
7475
if (makeAssertions) {
75-
cy
76-
.get('[data-test-id="frame"]', { timeout: 10000 })
77-
.should('have.length', 2)
76+
cy.get('[data-test-id="frame"]', { timeout: 10000 }).should(
77+
'have.length',
78+
2
79+
)
7880
cy.wait(500)
79-
cy
80-
.get('[data-test-id="frameCommand"]')
81+
cy.get('[data-test-id="frameCommand"]')
8182
.first()
8283
.should('contain', ':play start')
8384
cy.executeCommand(':clear')
@@ -94,12 +95,13 @@ Cypress.Commands.add('executeCommand', query => {
9495
cy.get(SubmitQueryButton).click()
9596
})
9697
Cypress.Commands.add('waitForCommandResult', () => {
97-
cy
98-
.get('[data-test-id="frame-loaded-contents"]', { timeout: 40000 })
99-
.should('be.visible')
98+
cy.get('[data-test-id="frame-loaded-contents"]', { timeout: 40000 }).should(
99+
'be.visible'
100+
)
100101
})
101102
Cypress.Commands.add('resultContains', str => {
102-
cy
103-
.get('[data-test-id="frameContents"]', { timeout: 40000 })
104-
.should('contain', str)
103+
cy.get('[data-test-id="frameContents"]', { timeout: 40000 }).should(
104+
'contain',
105+
str
106+
)
105107
})

src/browser/modules/User/RolesSelector.jsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@
2020
import React from 'react'
2121
import { StyledSelect } from './styled'
2222

23-
const RolesSelector = ({
24-
roles = [],
25-
onChange = null,
26-
selectedValue = undefined
27-
}) => {
23+
const RolesSelector = ({ roles = [], onChange = null, selectedValue = 0 }) => {
24+
let options = [
25+
<option key={'-1'} value={0}>
26+
{' '}
27+
</option>
28+
]
2829
if (roles.length > 0) {
29-
const options = roles.map((role, i) => {
30-
return (
31-
<option key={i} value={role}>
32-
{role}
33-
</option>
34-
)
35-
})
30+
options = options.concat(
31+
roles.map((role, i) => {
32+
return (
33+
<option key={i} value={role}>
34+
{role}
35+
</option>
36+
)
37+
})
38+
)
3639
return (
3740
<StyledSelect
3841
className='roles-selector'

src/browser/modules/User/UserInformation.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ export class UserInformation extends Component {
131131
roles={this.availableRoles()}
132132
onChange={this.onRoleSelect.bind(this)}
133133
/>
134+
</StyledUserTd>
135+
<StyledUserTd className='current-roles'>
134136
<span>{this.listRoles()}</span>
135137
</StyledUserTd>
136138
<StyledUserTd className='status'>

src/browser/modules/User/UserList.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export class UserList extends Component {
9494
})
9595
const tableHeaders = [
9696
'Username',
97-
'Roles(s)',
97+
'Add Role',
98+
'Current Roles(s)',
9899
'Status',
99100
'Password Change',
100101
'Delete'

src/shared/services/bolt/boltConnection.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,19 @@ const validateConnection = (driver, res, rej) => {
5252
res(driver)
5353
})
5454
.catch(e => {
55-
rej(e)
55+
// Only invalidate the connection if not available
56+
// or not authed
57+
// or credentials have expired
58+
const invalidStates = [
59+
'ServiceUnavailable',
60+
'Neo.ClientError.Security.Unauthorized',
61+
'Neo.ClientError.Security.CredentialsExpired'
62+
]
63+
if (!e.code || invalidStates.includes(e.code)) {
64+
rej(e)
65+
} else {
66+
res(driver)
67+
}
5668
})
5769
}
5870

0 commit comments

Comments
 (0)