diff --git a/src/components/ACLEditor/ACLEditor.react.js b/src/components/ACLEditor/ACLEditor.react.js
index 1f80cb156f..a1f46dc328 100644
--- a/src/components/ACLEditor/ACLEditor.react.js
+++ b/src/components/ACLEditor/ACLEditor.react.js
@@ -10,18 +10,58 @@ import PermissionsDialog from 'components/PermissionsDialog/PermissionsDialog.re
import React from 'react';
function validateEntry(text) {
- let userQuery = Parse.Query.or(
- new Parse.Query(Parse.User).equalTo('username', text),
- new Parse.Query(Parse.User).equalTo('objectId', text)
- );
- let roleQuery = new Parse.Query(Parse.Role).equalTo('name', text);
- return Promise.all([userQuery.find({ useMasterKey: true }), roleQuery.find({ useMasterKey: true })]).then(([user, role]) => {
+
+ let userQuery;
+ let roleQuery;
+
+ if (text === '*') {
+ return Promise.resolve({ entry: '*', type: 'public' });
+ }
+
+ if (text.startsWith('user:')) {
+ // no need to query roles
+ roleQuery = {
+ find: () => Promise.resolve([])
+ };
+
+ let user = text.substring(5);
+ userQuery = new Parse.Query.or(
+ new Parse.Query(Parse.User).equalTo('username', user),
+ new Parse.Query(Parse.User).equalTo('objectId', user)
+ );
+ } else if (text.startsWith('role:')) {
+ // no need to query users
+ userQuery = {
+ find: () => Promise.resolve([])
+ };
+ let role = text.substring(5);
+ roleQuery = new Parse.Query.or(
+ new Parse.Query(Parse.Role).equalTo('name', role),
+ new Parse.Query(Parse.Role).equalTo('objectId', role)
+ );
+ } else {
+ // query both
+ userQuery = Parse.Query.or(
+ new Parse.Query(Parse.User).equalTo('username', text),
+ new Parse.Query(Parse.User).equalTo('objectId', text)
+ );
+
+ roleQuery = Parse.Query.or(
+ new Parse.Query(Parse.Role).equalTo('name', text),
+ new Parse.Query(Parse.Role).equalTo('objectId', text)
+ );
+ }
+
+ return Promise.all([
+ userQuery.find({ useMasterKey: true }),
+ roleQuery.find({ useMasterKey: true })
+ ]).then(([user, role]) => {
if (user.length > 0) {
- return { user: user[0] };
+ return { entry: user[0], type: 'user' };
} else if (role.length > 0) {
- return { role: role[0] };
+ return { entry: role[0], type: 'role' };
} else {
- throw new Error();
+ return Promise.reject();
}
});
}
diff --git a/src/components/Autocomplete/Autocomplete.example.js b/src/components/Autocomplete/Autocomplete.example.js
new file mode 100644
index 0000000000..7ecec5ab61
--- /dev/null
+++ b/src/components/Autocomplete/Autocomplete.example.js
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2016-present, Parse, LLC
+ * All rights reserved.
+ *
+ * This source code is licensed under the license found in the LICENSE file in
+ * the root directory of this source tree.
+ */
+import React from 'react';
+import Autocomplete from 'components/Autocomplete/Autocomplete.react';
+
+export const component = Autocomplete;
+
+class AutocompleteDemo extends React.Component {
+ constructor() {
+ super();
+
+ this.state = {
+ suggestions: ['aaa', 'abc', 'xxx', 'xyz']
+ };
+
+ this.onSubmit = input => console.log('onSubmit: ' + input);
+ this.onUserInput = input => {
+ console.log(`input: ${input}`);
+ };
+ this.buildLabel = input =>
+ input.length > 0
+ ? `You've typed ${input.length} characters`
+ : 'Start typing';
+ this.buildSuggestions = input =>
+ this.state.suggestions.filter(s => s.startsWith(input));
+ }
+
+ render() {
+ return (
+
+ + {type.user.id} + {pill('User')} + +
++ {'username: '} + {type.user.name} +
+ + ); + } else if (type.role) { + label = ( + ++ + {'role:'} + {type.role.name} + +
++ id: {type.role.id} +
+ + ); + } else if (type.pointer) { + // get class info from schema + let { type, targetClass } = columns[key]; + + let pillText = type + (targetClass ? `<${targetClass}>` : ''); + + label = ( + ++ {key} + {pill(pillText)} +
+Only users pointed to by this field
+ + ); } + let content = null; if (!this.state.transitioning) { if (pointer) { @@ -511,15 +1031,23 @@ export default class PermissionsDialog extends React.Component { this.toggleField.bind(this) ); } else { - content = renderSimpleCheckboxes(key, this.state.perms, this.toggleField.bind(this)); + content = renderSimpleCheckboxes( + key, + this.state.perms, + this.toggleField.bind(this) + ); } } let trash = null; if (!this.state.transitioning) { trash = ( ); @@ -545,11 +1073,76 @@ export default class PermissionsDialog extends React.Component { this.toggleField.bind(this) ); } - return renderSimpleCheckboxes('*', this.state.perms, this.toggleField.bind(this)); + return renderSimpleCheckboxes( + '*', + this.state.perms, + this.toggleField.bind(this) + ); + } + + renderAuthenticatedCheckboxes() { + if (this.state.transitioning) { + return null; + } + if (this.props.advanced) { + return renderAdvancedCheckboxes( + 'requiresAuthentication', + this.state.perms, + this.state.level === 'Advanced', + this.toggleField.bind(this) + ); + } + return null; + } + + buildLabel(input) { + let label; + if (input.startsWith('userField:')) { + label = 'Name of field with pointer(s) to User'; + } else if (input.startsWith('user:')) { + label = 'Find User by id or name'; + } else if (input.startsWith('role:')) { + label = 'Find Role by id or name'; + } + + return label; + } + + suggestInput(input) { + // role: user: suggested if entry empty or does start with not already start with any of them + // and not - suggestedPrefix is currently set + const userPointers = this.props.userPointers || []; + + const keys = this.state.keys; + const newKeys = this.state.newKeys; + const allKeys = [...keys, ...newKeys]; + + // "userPointer:" fields that were not added yet + let unusedPointerFields = userPointers.filter( + ptr => !allKeys.includes(ptr) && ptr.includes(input) + ); + + // roles + let prefixes = ['role:'] + .filter(o => o.startsWith(input) && o.length > input.length) // filter matching input + .concat(...unusedPointerFields); + + // pointer fields that are not applied yet; + let availableFields = []; + + availableFields.push(...prefixes); + + return availableFields; } render() { let classes = [styles.dialog, unselectable]; + + // for 3-column CLP dialog + if (this.props.advanced) { + classes.push(styles.clp); + } + if (this.state.level === 'Advanced') { classes.push(styles.advanced); } @@ -562,16 +1155,31 @@ export default class PermissionsDialog extends React.Component { } return ( -+ + {'role:'} + {type.role.name} + +
++ id: {type.role.id} +
+ + ); + } + + if (type.user) { + label = ( + ++ + {type.user.id} + {pill('User')} + +
++ username:{' '} + {type.user.name} +
+ + ); + } + + if (type.public) { + label = ( + ++ {' '} + * (Public Access) +
+Applies to all queries
+ + ); + } + + if (type.auth) { + label = ( + +Authenticated
+Applies to any logged user
+ + ); + } + + if (type.pointer) { + let { type, targetClass } = columns[key.substring(10)]; + let pillText = type + (targetClass ? `<${targetClass}>` : ''); + + label = ( + ++ userField: + {key.substring(10)} + {pill(pillText)} +
+Only users pointed to by this field
+ + ); + } + + let content = null; + if (!this.state.transitioning) { + content = this.renderSelector( + key, + this.state.columns, + this.state.protectedFields.get(key) + ); + } + let trash = null; + if (!this.state.transitioning) { + trash = ( +