Skip to content

Commit c6b2c66

Browse files
add protected fields to Browser
1 parent 8e03cfe commit c6b2c66

File tree

3 files changed

+227
-31
lines changed

3 files changed

+227
-31
lines changed

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

Lines changed: 62 additions & 13 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,
@@ -143,6 +146,7 @@ let BrowserToolbar = ({
143146
onClick = null;
144147
}
145148

149+
const columns = {};
146150
const userPointers = [];
147151
const schemaSimplifiedData = {};
148152
const classSchema = schema.data.get('classes').get(classNameForEditors);
@@ -153,6 +157,8 @@ let BrowserToolbar = ({
153157
targetClass,
154158
};
155159

160+
columns[col] = { type, targetClass };
161+
156162
if (col === 'objectId' || isUnique && col !== uniqueField) {
157163
return;
158164
}
@@ -162,26 +168,33 @@ let BrowserToolbar = ({
162168
});
163169
}
164170

171+
let clpDialogRef = useRef(null);
172+
let protectedDialogRef = useRef(null);
173+
174+
const showCLP = ()=> clpDialogRef.current.handleOpen();
175+
const showProtected = () => protectedDialogRef.current.handleOpen();
176+
165177
return (
166178
<Toolbar
167179
relation={relation}
168180
filters={filters}
169-
section={relation ? `Relation <${relation.targetClassName}>` : 'Class'}
181+
section={relation ? `Relation <${relation.targetClassName}>` : "Class"}
170182
subsection={subsection}
171-
details={details.join(' \u2022 ')}
183+
details={details.join(" \u2022 ")}
172184
>
173-
<a className={classes.join(' ')} onClick={onClick}>
174-
<Icon name='plus-solid' width={14} height={14} />
185+
<a className={classes.join(" ")} onClick={onClick}>
186+
<Icon name="plus-solid" width={14} height={14} />
175187
<span>Add Row</span>
176188
</a>
177189
<div className={styles.toolbarSeparator} />
178190
<ColumnsConfiguration
179191
handleColumnsOrder={handleColumnsOrder}
180192
handleColumnDragDrop={handleColumnDragDrop}
181-
order={order} />
193+
order={order}
194+
/>
182195
<div className={styles.toolbarSeparator} />
183196
<a className={styles.toolbarButton} onClick={onRefresh}>
184-
<Icon name='refresh-solid' width={14} height={14} />
197+
<Icon name="refresh-solid" width={14} height={14} />
185198
<span>Refresh</span>
186199
</a>
187200
<div className={styles.toolbarSeparator} />
@@ -190,16 +203,52 @@ let BrowserToolbar = ({
190203
schema={schemaSimplifiedData}
191204
filters={filters}
192205
onChange={onFilterChange}
193-
className={classNameForEditors} />
206+
className={classNameForEditors}
207+
/>
194208
<div className={styles.toolbarSeparator} />
195-
{enableSecurityDialog ? <SecurityDialog
196-
setCurrent={setCurrent}
209+
<SecurityDialog
210+
ref={clpDialogRef}
211+
disabled={!!relation || !!isUnique}
212+
perms={perms}
213+
className={classNameForEditors}
214+
onChangeCLP={onChangeCLP}
215+
userPointers={userPointers}
216+
title="ClassLevelPermissions"
217+
icon="locked-solid"
218+
/>
219+
<SecureFieldsDialog
220+
ref={protectedDialogRef}
221+
columns={columns}
197222
disabled={!!relation || !!isUnique}
198223
perms={perms}
199224
className={classNameForEditors}
200225
onChangeCLP={onChangeCLP}
201-
userPointers={userPointers} /> : <noscript />}
202-
{enableSecurityDialog ? <div className={styles.toolbarSeparator} /> : <noscript/>}
226+
userPointers={userPointers}
227+
title="ProtectedFields"
228+
icon="locked-solid"
229+
/>
230+
{enableSecurityDialog ? (
231+
<BrowserMenu
232+
setCurrent={setCurrent}
233+
title="Security"
234+
icon="locked-solid"
235+
disabled={!!relation || !!isUnique}
236+
>
237+
<div className={classes.join(" ")} onClick={showCLP}>
238+
<span>{"ClassLevelPermissions"}</span>
239+
</div>
240+
<div className={classes.join(" ")} onClick={showProtected}>
241+
<span>{"ProtectedFields"}</span>
242+
</div>
243+
</BrowserMenu>
244+
) : (
245+
<noscript />
246+
)}
247+
{enableSecurityDialog ? (
248+
<div className={styles.toolbarSeparator} />
249+
) : (
250+
<noscript />
251+
)}
203252
{menu}
204253
</Toolbar>
205254
);
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: 16 additions & 18 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,44 +42,43 @@ 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;
4957
if (this.props.perms && this.state.open) {
5058
dialog = (
5159
<PermissionsDialog
52-
title='Edit Class Level Permissions'
60+
title='Edit Class Level Pesrmissions'
5361
enablePointerPermissions={parseServerSupportsPointerPermissions}
5462
advanced={true}
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)