Skip to content

Commit 8bcc8be

Browse files
add protected fields to Browser
1 parent 9134909 commit 8bcc8be

File tree

3 files changed

+220
-25
lines changed

3 files changed

+220
-25
lines changed

src/dashboard/Data/Browser/BrowserToolbar.react.js

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ import ColumnsConfiguration
1212
import Icon from 'components/Icon/Icon.react';
1313
import MenuItem from 'components/BrowserMenu/MenuItem.react';
1414
import prettyNumber from 'lib/prettyNumber';
15-
import React from 'react';
16-
import SecurityDialog from 'dashboard/Data/Browser/SecurityDialog.react';
15+
import React,
16+
{ useRef } from 'react';
1717
import Separator from 'components/BrowserMenu/Separator.react';
1818
import styles from 'dashboard/Data/Browser/Browser.scss';
1919
import Toolbar from 'components/Toolbar/Toolbar.react';
20+
import SecurityDialog from 'dashboard/Data/Browser/SecurityDialog.react';
21+
import SecureFieldsDialog
22+
from 'dashboard/Data/Browser/SecureFieldsDialog.react';
2023

2124
let BrowserToolbar = ({
2225
className,
@@ -152,6 +155,7 @@ let BrowserToolbar = ({
152155
onClick = null;
153156
}
154157

158+
const columns = {};
155159
const userPointers = [];
156160
const schemaSimplifiedData = {};
157161
const classSchema = schema.data.get('classes').get(classNameForEditors);
@@ -162,6 +166,8 @@ let BrowserToolbar = ({
162166
targetClass,
163167
};
164168

169+
columns[col] = { type, targetClass };
170+
165171
if (col === 'objectId' || isUnique && col !== uniqueField) {
166172
return;
167173
}
@@ -171,6 +177,12 @@ let BrowserToolbar = ({
171177
});
172178
}
173179

180+
let clpDialogRef = useRef(null);
181+
let protectedDialogRef = useRef(null);
182+
183+
const showCLP = ()=> clpDialogRef.current.handleOpen();
184+
const showProtected = () => protectedDialogRef.current.handleOpen();
185+
174186
return (
175187
<Toolbar
176188
relation={relation}
@@ -189,10 +201,11 @@ let BrowserToolbar = ({
189201
<ColumnsConfiguration
190202
handleColumnsOrder={handleColumnsOrder}
191203
handleColumnDragDrop={handleColumnDragDrop}
192-
order={order} />
204+
order={order}
205+
/>
193206
<div className={styles.toolbarSeparator} />
194207
<a className={styles.toolbarButton} onClick={onRefresh}>
195-
<Icon name='refresh-solid' width={14} height={14} />
208+
<Icon name="refresh-solid" width={14} height={14} />
196209
<span>Refresh</span>
197210
</a>
198211
<div className={styles.toolbarSeparator} />
@@ -204,14 +217,49 @@ let BrowserToolbar = ({
204217
className={classNameForEditors}
205218
blacklistedFilters={onAddRow ? [] : ['unique']} />
206219
{onAddRow && <div className={styles.toolbarSeparator} />}
207-
{perms && enableSecurityDialog ? <SecurityDialog
208-
setCurrent={setCurrent}
220+
<SecurityDialog
221+
ref={clpDialogRef}
209222
disabled={!!relation || !!isUnique}
210223
perms={perms}
211224
className={classNameForEditors}
212225
onChangeCLP={onChangeCLP}
213-
userPointers={userPointers} /> : <noscript />}
214-
{perms && enableSecurityDialog ? <div className={styles.toolbarSeparator} /> : <noscript/>}
226+
userPointers={userPointers}
227+
title="ClassLevelPermissions"
228+
icon="locked-solid"
229+
/>
230+
<SecureFieldsDialog
231+
ref={protectedDialogRef}
232+
columns={columns}
233+
disabled={!!relation || !!isUnique}
234+
perms={perms}
235+
className={classNameForEditors}
236+
onChangeCLP={onChangeCLP}
237+
userPointers={userPointers}
238+
title="ProtectedFields"
239+
icon="locked-solid"
240+
/>
241+
{enableSecurityDialog ? (
242+
<BrowserMenu
243+
setCurrent={setCurrent}
244+
title="Security"
245+
icon="locked-solid"
246+
disabled={!!relation || !!isUnique}
247+
>
248+
<div className={classes.join('')} onClick={showCLP}>
249+
<span>{'ClassLevelPermissions'}</span>
250+
</div>
251+
<div className={classes.join(' ')} onClick={showProtected}>
252+
<span>{'ProtectedFields'}</span>
253+
</div>
254+
</BrowserMenu>
255+
) : (
256+
<noscript />
257+
)}
258+
{enableSecurityDialog ? (
259+
<div className={styles.toolbarSeparator} />
260+
) : (
261+
<noscript />
262+
)}
215263
{menu}
216264
</Toolbar>
217265
);
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright (c) 2016-present, Parse, LLC
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the LICENSE file in
6+
* the root directory of this source tree.
7+
*/
8+
9+
import Parse from "parse";
10+
import React from "react";
11+
import styles from "dashboard/Data/Browser/Browser.scss";
12+
import ProtectedFieldsDialog from "components/ProtectedFieldsDialog/ProtectedFieldsDialog.react";
13+
14+
import ParseApp from "lib/ParseApp";
15+
import PropTypes from "prop-types";
16+
17+
function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
18+
if (parseServerSupportsPointerPermissions) {
19+
if (pointers.indexOf(text) > -1) {
20+
return Promise.resolve({ pointer: text });
21+
}
22+
}
23+
24+
let userQuery;
25+
let roleQuery;
26+
27+
// allow explicitly define whether it is a role or user
28+
// (e.g there might be both role 'admin' and user 'admin')
29+
// in such case you can type role:admin to query only roles
30+
if (text === "*" || text.toLowerCase() === "public") {
31+
return Promise.resolve({ public: "*" });
32+
}
33+
if (text.startsWith("user:")) {
34+
// no need to query roles
35+
roleQuery = {
36+
find: () => Promise.resolve([])
37+
};
38+
39+
let user = text.substring(5);
40+
userQuery = new Parse.Query.or(
41+
new Parse.Query(Parse.User).equalTo("username", user),
42+
new Parse.Query(Parse.User).equalTo("objectId", user)
43+
);
44+
} else if (text.startsWith("role:")) {
45+
// no need to query users
46+
userQuery = {
47+
find: () => Promise.resolve([])
48+
};
49+
let role = text.substring(5);
50+
roleQuery = new Parse.Query.or(
51+
new Parse.Query(Parse.Role).equalTo("name", role),
52+
new Parse.Query(Parse.Role).equalTo("objectId", role)
53+
);
54+
} else {
55+
// query both
56+
userQuery = Parse.Query.or(
57+
new Parse.Query(Parse.User).equalTo("username", text),
58+
new Parse.Query(Parse.User).equalTo("objectId", text)
59+
);
60+
61+
roleQuery = Parse.Query.or(
62+
new Parse.Query(Parse.Role).equalTo("name", text),
63+
new Parse.Query(Parse.Role).equalTo("objectId", text)
64+
);
65+
}
66+
67+
return Promise.all([
68+
userQuery.find({ useMasterKey: true }),
69+
roleQuery.find({ useMasterKey: true })
70+
]).then(([user, role]) => {
71+
if (user.length > 0) {
72+
return { user: user[0] };
73+
} else if (role.length > 0) {
74+
return { role: role[0] };
75+
} else {
76+
return Promise.reject();
77+
}
78+
});
79+
}
80+
81+
export default class SecureFieldsDialog extends React.Component {
82+
constructor(props) {
83+
super(props);
84+
this.state = { open: false };
85+
}
86+
87+
componentDidUpdate(prevProps) {
88+
if (prevProps.perms !== this.props.perms) {
89+
this.setState();
90+
}
91+
}
92+
93+
handleOpen() {
94+
if (!this.props.disabled) {
95+
this.setState({ open: true });
96+
}
97+
}
98+
99+
render() {
100+
let dialog = null;
101+
let parseServerSupportsPointerPermissions = this.context.currentApp
102+
.serverInfo.features.schemas.editClassLevelPermissions;
103+
if (this.props.perms && this.state.open) {
104+
dialog = (
105+
<ProtectedFieldsDialog
106+
title="Edit Protected Fields"
107+
columns={this.props.columns}
108+
protectedFields={this.props.perms.protectedFields}
109+
enablePointerPermissions={parseServerSupportsPointerPermissions}
110+
advanced={true}
111+
confirmText="Save Fields"
112+
details={
113+
<a
114+
target="_blank"
115+
href="http://docs.parseplatform.org/ios/guide/#security"
116+
>
117+
Learn more about CLPs and app security
118+
</a>
119+
}
120+
validateEntry={entry =>
121+
validateEntry(
122+
this.props.userPointers,
123+
entry,
124+
parseServerSupportsPointerPermissions
125+
)
126+
}
127+
onCancel={() => {
128+
this.setState({ open: false });
129+
}}
130+
onConfirm={perms =>
131+
this.props
132+
.onChangeCLP(perms)
133+
.then(() => this.setState({ open: false }))
134+
}
135+
/>
136+
);
137+
}
138+
let classes = [styles.toolbarButton];
139+
if (this.props.disabled) {
140+
classes.push(styles.toolbarButtonDisabled);
141+
}
142+
143+
return dialog;
144+
}
145+
}
146+
147+
SecureFieldsDialog.contextTypes = {
148+
currentApp: PropTypes.instanceOf(ParseApp)
149+
};

src/dashboard/Data/Browser/SecurityDialog.react.js

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* the root directory of this source tree.
77
*/
88
import PropTypes from 'lib/PropTypes';
9-
import Icon from 'components/Icon/Icon.react';
109
import Parse from 'parse'
1110
import ParseApp from 'lib/ParseApp';
1211
import PermissionsDialog from 'components/PermissionsDialog/PermissionsDialog.react';
@@ -43,6 +42,15 @@ export default class SecurityDialog extends React.Component {
4342
this.state = { open: false };
4443
}
4544

45+
/**
46+
* Allows opening this dialog by reference
47+
*/
48+
handleOpen(){
49+
if (!this.props.disabled) {
50+
this.setState({ open: true });
51+
}
52+
}
53+
4654
render() {
4755
let dialog = null;
4856
let parseServerSupportsPointerPermissions = this.context.currentApp.serverInfo.features.schemas.editClassLevelPermissions;
@@ -55,32 +63,22 @@ export default class SecurityDialog extends React.Component {
5563
confirmText='Save CLP'
5664
details={<a target="_blank" href='http://docs.parseplatform.org/ios/guide/#security'>Learn more about CLPs and app security</a>}
5765
permissions={this.props.perms}
58-
validateEntry={entry => validateEntry(this.props.userPointers, entry, parseServerSupportsPointerPermissions)}
66+
validateEntry={entry =>
67+
validateEntry(this.props.userPointers, entry, parseServerSupportsPointerPermissions)}
5968
onCancel={() => {
6069
this.setState({ open: false });
6170
}}
62-
onConfirm={perms => this.props.onChangeCLP(perms).then(() => this.setState({ open: false }))}
71+
onConfirm={perms =>
72+
this.props.onChangeCLP(perms).then(() => this.setState({ open: false }))}
6373
/>
6474
);
6575
}
6676
let classes = [styles.toolbarButton];
6777
if (this.props.disabled) {
6878
classes.push(styles.toolbarButtonDisabled);
6979
}
70-
let onClick = null;
71-
if (!this.props.disabled) {
72-
onClick = () => {
73-
this.setState({ open: true });
74-
this.props.setCurrent(null);
75-
};
76-
}
77-
return (
78-
<div className={classes.join(' ')} onClick={onClick}>
79-
<Icon width={14} height={14} name='locked-solid' />
80-
<span>Security</span>
81-
{dialog}
82-
</div>
83-
);
80+
81+
return dialog;
8482
}
8583
}
8684

0 commit comments

Comments
 (0)