Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(Roles & Permissions): initial support #2111

Closed
Closed
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
32 changes: 23 additions & 9 deletions admin/client/components/EditForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,35 @@ var EditForm = React.createClass({
}, this);
},
renderFooterBar () {
var buttons = [
<Button key="save" type="primary" submit>Save</Button>
];
buttons.push(
<Button key="reset" onClick={this.confirmReset} type="link-cancel">
<ResponsiveText hiddenXS="reset changes" visibleXS="reset" />
</Button>
);
if (!this.props.list.nodelete) {
if (this.props.list.noedit) return null;

let hasListUpdatePermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.list.key].roles.update.indexOf(n) != -1;
}).length > 0;

if (hasListUpdatePermissions) {
var buttons = [
<Button key="save" type="primary" submit>Save</Button>
];
buttons.push(
<Button key="reset" onClick={this.confirmReset} type="link-cancel">
<ResponsiveText hiddenXS="reset changes" visibleXS="reset"/>
</Button>
);
}

let hasListDeletePermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.list.key].roles.delete.indexOf(n) != -1;
}).length > 0;

if (hasListDeletePermissions) {
buttons.push(
<Button key="del" onClick={this.confirmDelete} type="link-delete" className="u-float-right">
<ResponsiveText hiddenXS={`delete ${this.props.list.singular.toLowerCase()}`} visibleXS="delete" />
</Button>
);
}

return (
<FooterBar className="EditForm__footer">
{buttons}
Expand Down
6 changes: 6 additions & 0 deletions admin/client/components/EditFormHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ var Header = React.createClass({
renderCreateButton () {
if (this.props.list.nocreate) return null;

let hasListCreatePermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.list.key].roles.create.indexOf(n) != -1;
}).length > 0;

if (!hasListCreatePermissions) return null;

var props = {};
if (this.props.list.autocreate) {
props.href = '?new' + Keystone.csrf.query;
Expand Down
15 changes: 13 additions & 2 deletions admin/client/components/ItemsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ const ItemsTable = React.createClass({
columns: React.PropTypes.array,
items: React.PropTypes.object,
list: React.PropTypes.object,
user: React.PropTypes.object,
permissions: React.PropTypes.object,
},
renderCols () {
var cols = this.props.columns.map((col) => <col width={col.width} key={col.path} />);

let hasListDeletePermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.list.key].roles.delete.indexOf(n) != -1;
}).length > 0;

// add delete col when applicable
if (!this.props.list.nodelete) {
if (!this.props.list.nodelete && hasListDeletePermissions) {
cols.unshift(<col width={TABLE_CONTROL_COLUMN_WIDTH} key="delete" />);
}
// add sort col when applicable
Expand All @@ -28,12 +35,16 @@ const ItemsTable = React.createClass({
return <colgroup>{cols}</colgroup>;
},
renderHeaders () {
let hasListDeletePermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.list.key].roles.delete.indexOf(n) != -1;
}).length > 0;

var cells = this.props.columns.map((col, i) => {
// span first col for controls when present
var span = 1;
if (!i) {
if (this.props.list.sortable) span++;
if (!this.props.list.nodelete) span++;
if (!this.props.list.nodelete || hasListDeletePermissions) span++;
}
return <th key={col.path} colSpan={span}>{col.label}</th>;
});
Expand Down
6 changes: 5 additions & 1 deletion admin/client/components/ItemsTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ const ItemsRow = React.createClass({
}

// add delete/check icon when applicable
if (!this.props.list.nodelete) {
let hasListDeletePermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.list.key].roles.delete.indexOf(n) != -1;
}).length > 0;

if (!this.props.list.nodelete && hasListDeletePermissions) {
cells.unshift(this.props.manageMode ? (
<ListControl key="_check" type="check" active={this.props.checkedItems[itemId]} />
) : (
Expand Down
12 changes: 12 additions & 0 deletions admin/client/components/MobileNavigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ var MobileNavigation = React.createClass({
currentListKey: React.PropTypes.string,
sections: React.PropTypes.array.isRequired,
signoutUrl: React.PropTypes.string,
user: React.PropTypes.object.isRequired,
permissions: React.PropTypes.object.isRequired
},
getInitialState () {
return {
Expand Down Expand Up @@ -116,6 +118,16 @@ var MobileNavigation = React.createClass({
return this.props.sections.map((section) => {
let href = section.lists[0].external ? section.lists[0].path : `${Keystone.adminPath}/${section.lists[0].path}`;
let className = (this.props.currentSectionKey && this.props.currentSectionKey === section.key) ? 'MobileNavigation__section is-active' : 'MobileNavigation__section';
let hasPermissionsToReadSomeListsInSection = false;

section.lists.map((list) => {
if (hasPermissionsToReadSomeListsInSection) return;
hasPermissionsToReadSomeListsInSection = this.props.user.roles.filter((n) => {
return this.props.permissions[list.key].roles.read.indexOf(n) != -1;
}).length > 0;
});

if (!hasPermissionsToReadSomeListsInSection) return null;

return (
<MobileSectionItem key={section.key} className={className} href={href} lists={section.lists} currentListKey={this.props.currentListKey}>
Expand Down
12 changes: 12 additions & 0 deletions admin/client/components/PrimaryNavigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ var PrimaryNavigation = React.createClass({
brand: React.PropTypes.string,
sections: React.PropTypes.array.isRequired,
signoutUrl: React.PropTypes.string,
user: React.PropTypes.object.isRequired,
permissions: React.PropTypes.object.isRequired
},
getInitialState() {
return {};
Expand Down Expand Up @@ -76,6 +78,16 @@ var PrimaryNavigation = React.createClass({
return this.props.sections.map((section) => {
let href = section.lists[0].external ? section.lists[0].path : `${Keystone.adminPath}/${section.lists[0].path}`;
let className = (this.props.currentSectionKey && this.props.currentSectionKey === section.key) ? 'active' : null;
let hasPermissionsToReadSomeListsInSection = false;

section.lists.map((list) => {
if (hasPermissionsToReadSomeListsInSection) return;
hasPermissionsToReadSomeListsInSection = this.props.user.roles.filter((n) => {
return this.props.permissions[list.key].roles.read.indexOf(n) != -1;
}).length > 0;
});

if (!hasPermissionsToReadSomeListsInSection) return null;

return (
<PrimaryNavItem key={section.key} className={className} href={href}>
Expand Down
8 changes: 8 additions & 0 deletions admin/client/components/SecondaryNavigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var SecondaryNavigation = React.createClass({
propTypes: {
currentListKey: React.PropTypes.string,
lists: React.PropTypes.array.isRequired,
user: React.PropTypes.object.isRequired,
permissions: React.PropTypes.object.isRequired
},
getInitialState() {
return {};
Expand All @@ -46,6 +48,12 @@ var SecondaryNavigation = React.createClass({
let href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
let className = (this.props.currentListKey && this.props.currentListKey === list.path) ? 'active' : null;

let hasListReadPermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[list.key].roles.read.indexOf(n) != -1;
}).length > 0;

if (!hasListReadPermissions) return null;

return (
<SecondaryNavItem key={list.path} className={className} href={href}>
{list.label}
Expand Down
70 changes: 62 additions & 8 deletions admin/client/views/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,32 @@ var ListTile = React.createClass({
count: React.PropTypes.string,
href: React.PropTypes.string,
label: React.PropTypes.string,
listkey: React.PropTypes.string
},
render () {
let canCreateLists = this.props.user.roles.filter((n) => {
return this.props.permissions[this.props.listkey].roles.create.indexOf(n) != -1;
}).length > 0;

let renderCreateLink = null;
if (canCreateLists) {
renderCreateLink = (<a href={this.props.href + '?create'}
className="dashboard-group__list-create octicon octicon-plus"
title="Create" tabIndex="-1"/>);
}

return (
<div className="dashboard-group__list">
<span className="dashboard-group__list-inner">
<a href={this.props.href} className="dashboard-group__list-tile">
<div className="dashboard-group__list-label">{this.props.label}</div>
<div className="dashboard-group__list-count">{this.props.count}</div>
</a>
<a href={this.props.href + '?create'} className="dashboard-group__list-create octicon octicon-plus" title="Create" tabIndex="-1" />
{renderCreateLink}
</span>
</div>
);
},
}
});

var HomeView = React.createClass({
Expand Down Expand Up @@ -90,15 +102,33 @@ var HomeView = React.createClass({
},
renderFlatNav () {
let lists = this.props.navLists.map((list) => {
var href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
return <ListTile key={list.path} label={list.label} href={href} count={plural(this.state.counts[list.key], '* Item', '* Items')} />;
var hasListReadPermissions = this.props.user.roles.filter((n) => {
return this.props.permissions[list.key].roles.read.indexOf(n) != -1;
});

if (hasListReadPermissions.length > 0) {
var href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
return (<ListTile listkey={list.key} label={list.label} href={href}
count={plural(this.state.counts[list.key], '* Item', '* Items')}
user={this.props.user} permissions={this.props.permissions} />);
}
});
return <div className="dashboard-group__lists">{lists}</div>;
},
renderGroupedNav () {
return (
<div>
{this.props.navSections.map((navSection) => {
let hasListReadPermissions = {};
let hasReadPermissionsForSomeLists = false;
{navSection.lists.map((list) => {
hasListReadPermissions[list.key] = this.props.user.roles.filter((n) => {
return this.props.permissions[list.key].roles.read.indexOf(n) != -1;
}).length > 0;
hasReadPermissionsForSomeLists = hasReadPermissionsForSomeLists ? hasReadPermissionsForSomeLists : hasListReadPermissions[list.key];
});}
if (!hasReadPermissionsForSomeLists) return;

return (
<div className="dashboard-group" key={navSection.key}>
<div className="dashboard-group__heading">
Expand All @@ -107,8 +137,12 @@ var HomeView = React.createClass({
</div>
<div className="dashboard-group__lists">
{navSection.lists.map((list) => {
var href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
return <ListTile key={list.path} label={list.label} href={href} count={plural(this.state.counts[list.key], '* Item', '* Items')} />;
if (hasListReadPermissions[list.key]) {
var href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
return (<ListTile listkey={list.key} label={list.label} href={href}
count={plural(this.state.counts[list.key], '* Item', '* Items')}
user={this.props.user} permissions={this.props.permissions} />);
}
})}
</div>
</div>
Expand All @@ -120,6 +154,17 @@ var HomeView = React.createClass({
},
renderOrphanedLists () {
if (!this.props.orphanedLists.length) return;

let hasListReadPermissions = {};
let hasReadPermissionsForSomeList = false;
this.props.orphanedLists.map((list) => {
hasListReadPermissions[list.key] = this.props.user.roles.filter((n) => {
return this.props.permissions[list.key].roles.read.indexOf(n) != -1;
}).length > 0;
hasReadPermissionsForSomeList = hasListReadPermissions[list.key] ? true : hasReadPermissionsForSomeList;
});
if (!hasReadPermissionsForSomeList) return;

return (
<div className="dashboard-group">
<div className="dashboard-group__heading">
Expand All @@ -128,8 +173,12 @@ var HomeView = React.createClass({
</div>
<div className="dashboard-group__lists">
{this.props.orphanedLists.map((list) => {
var href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
return <ListTile key={list.path} label={list.label} href={href} count={plural(this.state.counts[list.key], '* Item', '* Items')} />;
if (hasListReadPermissions[list.key]) {
var href = list.external ? list.path : `${Keystone.adminPath}/${list.path}`;
return (<ListTile listkey={list.key} label={list.label} href={href}
count={plural(this.state.counts[list.key], '* Item', '* Items')}
user={this.props.user} permissions={this.props.permissions} />);
}
})}
</div>
</div>
Expand All @@ -144,12 +193,16 @@ var HomeView = React.createClass({
currentSectionKey="dashboard"
sections={this.props.nav.sections}
signoutUrl={this.props.signoutUrl}
user={this.props.user}
permissions={this.props.permissions}
/>
<PrimaryNavigation
brand={this.props.brand}
currentSectionKey="dashboard"
sections={this.props.nav.sections}
signoutUrl={this.props.signoutUrl}
user={this.props.user}
permissions={this.props.permissions}
/>
</header>
<div className="keystone-body">
Expand Down Expand Up @@ -187,6 +240,7 @@ ReactDOM.render(
signoutUrl={Keystone.signoutUrl}
User={Keystone.User}
user={Keystone.user}
permissions={Keystone.permissions}
version={Keystone.version}
/>,
document.getElementById('home-view')
Expand Down
23 changes: 19 additions & 4 deletions admin/client/views/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,33 @@ var ItemView = React.createClass({
currentSectionKey={this.props.nav.currentSection.key}
sections={this.props.nav.sections}
signoutUrl={this.props.signoutUrl}
user={this.props.user}
permissions={this.props.permissions}
/>
<PrimaryNavigation
currentSectionKey={this.props.nav.currentSection.key}
brand={this.props.brand}
sections={this.props.nav.sections}
signoutUrl={this.props.signoutUrl} />
signoutUrl={this.props.signoutUrl}
user={this.props.user}
permissions={this.props.permissions}
/>
<SecondaryNavigation
currentListKey={this.props.list.path}
lists={this.props.nav.currentSection.lists} />
lists={this.props.nav.currentSection.lists}
user={this.props.user}
permissions={this.props.permissions}
/>
</header>
<div className="keystone-body">
<EditFormHeader
list={this.props.list}
data={this.state.itemData}
drilldown={this.state.itemDrilldown}
toggleCreate={this.toggleCreate} />
toggleCreate={this.toggleCreate}
user={this.props.user}
permissions={this.props.permissions}
/>
<Container>
<CreateForm
list={this.props.list}
Expand All @@ -91,7 +102,10 @@ var ItemView = React.createClass({
messages={this.props.messages} />
<EditForm
list={this.props.list}
data={this.state.itemData} />
data={this.state.itemData}
user={this.props.user}
permissions={this.props.permissions}
/>
{this.renderRelationships()}
</Container>
</div>
Expand Down Expand Up @@ -119,6 +133,7 @@ ReactDOM.render(
signoutUrl={Keystone.signoutUrl}
User={Keystone.User}
user={Keystone.user}
permissions={Keystone.permissions}
version={Keystone.version}
/>,
document.getElementById('item-view')
Expand Down
Loading