RBAC React Component, displays/hides components based on role
authorLaurence Maultsby <laurence.maultsby@riftio.com>
Thu, 6 Apr 2017 14:42:55 +0000 (10:42 -0400)
committerLaurence Maultsby <laurence.maultsby@riftio.com>
Thu, 6 Apr 2017 14:42:55 +0000 (10:42 -0400)
Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
14 files changed:
skyquake/framework/core/modules/api/projectManagementAPI.js
skyquake/framework/core/modules/api/userManagementAPI.js
skyquake/framework/core/modules/navigation_manager.js
skyquake/framework/core/modules/routes/projectManagement.js
skyquake/framework/core/modules/routes/userManagement.js
skyquake/framework/utils/roleConstants.js [new file with mode: 0644]
skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
skyquake/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/dashboard.jsx
skyquake/plugins/project_management/src/dashboard/projectMgmt.scss
skyquake/plugins/project_management/src/dashboard/projectMgmtStore.js
skyquake/plugins/user_management/config.json
skyquake/plugins/user_management/src/dashboard/dashboard.jsx
skyquake/plugins/user_management/src/dashboard/userMgmt.scss

index 3238aec..07e873d 100644 (file)
@@ -178,14 +178,18 @@ ProjectManagement.delete = function(req) {
 }
 
 
-ProjectManagement.getPlatform = function(req) {
+ProjectManagement.getPlatform = function(req, userId) {
     var self = this;
     var api_server = req.query['api_server'];
-
+    var user = req.params['userId'] || userId;
     return new Promise(function(resolve, reject) {
+        var url = utils.confdPort(api_server) + '/api/operational/rbac-platform-config';
+        if(user) {
+            url = url + '/user/' + user;
+        }
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operational/rbac-platform-config',
+                uri: url,
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
                     'Authorization': req.session && req.session.authorization
@@ -198,7 +202,11 @@ ProjectManagement.getPlatform = function(req) {
             var response = {};
             response['data'] = {};
             if (result[0].body) {
-                response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:rbac-platform-config'];
+                if(user) {
+                    response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:user'];
+                } else {
+                    response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:rbac-platform-config'];
+                }
             }
             response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
 
index 0608c5d..873ab19 100644 (file)
@@ -25,6 +25,7 @@ var Promise = require('promise');
 var constants = require('../../api_utils/constants');
 var utils = require('../../api_utils/utils');
 var _ = require('lodash');
+var ProjectManagementAPI = require('./projectManagementAPI.js');
 
 UserManagement.get = function(req) {
     var self = this;
@@ -63,20 +64,96 @@ UserManagement.get = function(req) {
     });
 };
 
+
 UserManagement.getProfile = function(req) {
     var self = this;
     var api_server = req.query['api_server'];
     return new Promise(function(resolve, reject) {
         var response = {};
-            response['data'] = {
-            userId: req.session.userdata.username,
+        var userId = req.session.userdata.username
+        response['data'] = {
+            userId: userId,
             projectId: req.session.projectId
         };
-        response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+        UserManagement.getUserInfo(req, userId).then(function(result) {
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK;
+            response.data.data =result.data
+            resolve(response);
+        }, function(error) {
+            console.log('Error retrieving getUserInfo');
+            response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR;
+            reject(response);
+        })
 
-        resolve(response);
     });
 };
+UserManagement.getUserInfo = function(req, userId, domain) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var id = req.params['userId'] || userId;
+    var domain = req.params['domainId'] || domain;
+    var response = {};
+    return new Promise(function(resolve, reject) {
+        if (id) {
+            var getProjects = ProjectManagementAPI.get(req)
+            var getPlatformUser = ProjectManagementAPI.getPlatform(req, id)
+            Promise.all([
+                getProjects,
+                getPlatformUser
+            ]).then(function(result) {
+                var userData = {
+                    platform: {
+                        role: {
+
+                        }
+                    },
+                    project: {
+                        /**
+                         *  [projectId] : {
+                         *      data: [project object],
+                         *      role: {
+                         *          [roleId]: true
+                         *      }
+                         *  }
+                         */
+                    }
+                }
+                //Build project roles
+                var projects = result[0].data.project;
+                var userProjects = [];
+                projects && projects.map(function(p, i) {
+                    var users = p['project-config'] && p['project-config'].user;
+                    users && users.map(function(u) {
+                        if(u['user-name'] == id) {
+                            userData.project[p.name] = {
+                                data: p,
+                                role: {}
+                            }
+                            u.role.map(function(r) {
+                                userData.project[p.name].role[r.role] = true
+                            });
+                        }
+                    })
+                });
+                //Build platform roles
+                var platformRoles = result[1].data.platform && result[1].data.platform.role;
+                platformRoles && platformRoles.map(function(r) {
+                    userData.platform.role[r.role] = true
+                });
+                response.data = userData;
+                response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+                resolve(response);
+            })
+        } else {
+            var errorMsg = 'userId not specified in UserManagement.getUserInfo';
+            console.error(errorMsg);
+            response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST;
+            response.error = errorMsg;
+            reject(response)
+        }
+
+    })
+}
 UserManagement.create = function(req) {
     var self = this;
     var api_server = req.query['api_server'];
index 6ed1e49..f690359 100644 (file)
@@ -68,6 +68,13 @@ function addLabel(plugin_name, label) {
        NAVIGATION[plugin_name].label = label || 'RW.UI Plugin';
 }
 
+function addAllow(plugin_name, allow) {
+       if (!NAVIGATION[plugin_name]) {
+               NAVIGATION[plugin_name] = {};
+       }
+       NAVIGATION[plugin_name].allow = allow || '*';
+}
+
 function getNavigation() {
        return NAVIGATION;
 }
@@ -81,6 +88,7 @@ function onNavigationDiscovered(plugin_name, plugin) {
        addOrder(plugin_name, plugin.order);
        addPriority(plugin_name, plugin.priority);
        addLabel(plugin_name, plugin.name);
+       addAllow(plugin_name, plugin.allow);
 }
 
 function init() {
index 4e272d0..22a2d74 100644 (file)
@@ -50,6 +50,13 @@ Router.get('/user-profile', cors(), function(req, res) {
         utils.sendErrorResponse(error, res);
     });
 });
+Router.get('/user-data/:userId/:domain?', cors(), function(req, res) {
+    UserManagementAPI.getUserInfo(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
 Router.post('/user', cors(), function(req, res) {
     UserManagementAPI.create(req).then(function(response) {
         utils.sendSuccessResponse(response, res);
diff --git a/skyquake/framework/utils/roleConstants.js b/skyquake/framework/utils/roleConstants.js
new file mode 100644 (file)
index 0000000..95e36fc
--- /dev/null
@@ -0,0 +1,16 @@
+var c = {};
+
+c.PLATFORM = {
+  OPER: "rw-rbac-platform:platform-oper",
+  ADMIN: "rw-rbac-platform:platform-admin",
+  SUPER: "rw-rbac-platform:super-admin"
+}
+
+c.PROJECT = {
+    MANO_OPER: "rw-project-mano:mano-oper",
+    MANO_ADMIN: "rw-project-mano:mano-admin",
+    PROJECT_ADMIN: "rw-project:project-admin",
+    PROJECT_OPER: "rw-project:project-oper",
+}
+
+module.exports = c;
index 51a21a0..a752bf6 100644 (file)
@@ -25,6 +25,7 @@ import 'style/common.scss';
 import './skyquakeNav.scss';
 import SelectOption from '../form_controls/selectOption.jsx';
 import {FormSection} from '../form_controls/formControls.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
 
 //Temporary, until api server is on same port as webserver
 var rw = require('utils/rw.js');
@@ -298,14 +299,14 @@ export function buildNav(nav, currentPlugin, props) {
             navItem.priority = nav[k].priority;
             navItem.order = nav[k].order;
             navItem.html = (
-                <div key={k} className={navClass}>
+                <SkyquakeRBAC allow={nav[k].allow || ['*']} key={k} className={navClass}>
                     <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
                     <ul className="menu">
                         {
                             NavList
                         }
                     </ul>
-                </div>
+                </SkyquakeRBAC>
             );
             navList.push(navItem)
         }
diff --git a/skyquake/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx b/skyquake/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx
new file mode 100644 (file)
index 0000000..e00e672
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+
+import React from 'react';
+import ROLES from 'utils/roleConstants.js';
+const PLATFORM = ROLES.PLATFORM;
+
+export default class SkyquakeRBAC extends React.Component {
+    constructor(props, context) {
+        super(props);
+    }
+    render() {
+      const User = this.context.userProfile.data;
+      let HTML = null;
+      // If user object has platform property then it has been populated by the back end.
+      if(User) {
+        const PlatformRole = User.platform.role;
+        const isPlatformSuper = PlatformRole[PLATFORM.SUPER];
+        const isPlatformAdmin = PlatformRole[PLATFORM.ADMIN];
+        const isPlatformOper = PlatformRole[PLATFORM.OPER];
+        const hasRoleAccess =  checkForRoleAccess(User.project[this.props.project], PlatformRole, this.props.allow)//false//(this.props.roles.indexOf(userProfile.projectRole) > -1)
+        if (isPlatformSuper) {
+          HTML = this.props.children;
+        } else {
+          if (hasRoleAccess) {
+            HTML = this.props.children;
+          }
+        }
+      }
+      return (<div className={this.props.className} style={this.props.style}>{HTML}</div>)
+    }
+}
+SkyquakeRBAC.defaultProps = {
+  allow: []
+}
+SkyquakeRBAC.contextTypes = {
+  userProfile: React.PropTypes.object
+}
+
+function checkForRoleAccess(project, PlatformRole, allow) {
+    if (allow.indexOf('*') > -1) return true;
+    for (let i = 0; i<allow.length; i++) {
+      if((project && project.role[allow[i]] )|| PlatformRole[allow[i]]) {
+        return true
+      }
+    }
+    return false;
+  }
+
+
+
+// export default function(Component) {
+//   class SkyquakeRBAC extends React.Component {
+//     constructor(props, context) {
+//         super(props);
+//             }
+//     render(props) {
+//       console.log(this.context.userProfile)
+//       const User = this.context.userProfile.data;
+//       // If user object has platform property then it has been populated by the back end.
+//       if(User) {
+//         const PlatformRole = User.platform.role;
+//         const HTML = <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux}/>;
+//         const isPlatformSuper = PlatformRole[PLATFORM.SUPER];
+//         const isPlatformAdmin = PlatformRole[PLATFORM.ADMIN];
+//         const isPlatformOper = PlatformRole[PLATFORM.OPER];
+//         const hasRoleAccess =  false//(this.props.roles.indexOf(userProfile.projectRole) > -1)
+//         if (isPlatformSuper || isPlatformOper || isPlatformAdmin) {
+//           return HTML
+//         } else {
+//           if (hasRoleAccess) {
+//             return HTML
+//           } else {
+//             return null;
+//           }
+//         }
+//       }
+//       else {
+//         return null;
+
+//       }
+//     }
+//   }
+//   SkyquakeRBAC.defaultProps = {
+
+//   }
+//   SkyquakeRBAC.contextTypes = {
+//     userProfile: React.PropTypes.object,
+//     allowedRoles: []
+//   };
+//   return SkyquakeRBAC;
+// }
index 8cda802..f1fca65 100644 (file)
@@ -7,6 +7,7 @@ import ReactDOM from 'react-dom';
 import AppHeader from 'widgets/header/header.jsx';
 import ProjectManagementStore from './projectMgmtStore.js';
 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
 import 'style/layout.scss';
 import './projectMgmt.scss';
 import {Panel, PanelWrapper} from 'widgets/panel/panel';
@@ -20,6 +21,10 @@ import 'widgets/form_controls/formControls.scss';
 import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
 import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
 
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
 class ProjectManagementDashboard extends React.Component {
     constructor(props) {
         super(props);
@@ -103,7 +108,6 @@ class ProjectManagementDashboard extends React.Component {
             'name': projectName,
             'description': self.state.description,
             'project-config' : {
-                'name-ref': projectName,
                 'user': cleanUsers
             }
         });
@@ -121,7 +125,6 @@ class ProjectManagementDashboard extends React.Component {
             'name': projectName,
             'description': self.state.description,
             'project-config' : {
-                'name-ref': projectName,
                 'user': cleanUsers
             }
         }));
@@ -214,9 +217,9 @@ class ProjectManagementDashboard extends React.Component {
         let state = this.state;
         let passwordSectionHTML = null;
         let formButtonsHTML = (
-            <ButtonGroup className="buttonGroup">
-                <Button label="EDIT" type="submit" onClick={this.editProject} />
-            </ButtonGroup>
+                <ButtonGroup className="buttonGroup">
+                    <Button label="EDIT" type="submit" onClick={this.editProject} />
+                </ButtonGroup>
         );
         let projectUsers = [];
         self.state.projectUsers.map((u) => {
@@ -282,9 +285,11 @@ class ProjectManagementDashboard extends React.Component {
                             )
                         })}
                     </Panel>
-                    <ButtonGroup  className="buttonGroup" style={{margin: '0 0.5rem 0.5rem', background: '#ddd', paddingBottom: '0.5rem'}}>
-                        <Button label="Add Project" onClick={this.addProject} />
-                    </ButtonGroup>
+                    <SkyquakeRBAC className="rbacButtonGroup">
+                        <ButtonGroup  className="buttonGroup">
+                            <Button label="Add Project" onClick={this.addProject} />
+                        </ButtonGroup>
+                    </SkyquakeRBAC>
                 </PanelWrapper>
                 <PanelWrapper onKeyUp={this.evaluateSubmit}
                     className={`ProjectAdmin column`}>
@@ -368,8 +373,9 @@ class ProjectManagementDashboard extends React.Component {
                         </FormSection>
 
                     </Panel>
+                     <SkyquakeRBAC allow={[PROJECT_ROLES.PROJECT_ADMIN]} project={this.state.name} className="rbacButtonGroup">
                         {formButtonsHTML}
-
+                     </SkyquakeRBAC>
                 </PanelWrapper>
 
 
index 82b62a8..a21aaa9 100644 (file)
 
         }
     }
-    .buttonGroup {
+    .rbacButtonGroup {
         margin: 0 0.5rem 0.5rem;
         background: #ddd;
         padding-bottom: 0.5rem;
         padding: 0.5rem 0;
+    }
+    .buttonGroup {
         border-top: #d3d3d3 1px solid;
+        padding-top:0.5rem;
     }
     .addUser {
         display:-ms-flexbox;
index 64761dc..ec3a099 100644 (file)
@@ -59,7 +59,7 @@ export default class ProjectManagementStore {
         let project = data[0];
         let projectIndex = data[1];
 
-        let ProjectUser = {
+        let ProjectData = {
             'name': project['name'],
             'description': project['description'],
             'projectUsers': project['project-config'] && project['project-config']['user'] || []
@@ -69,7 +69,7 @@ export default class ProjectManagementStore {
             projectOpen: true,
             isEdit: true,
             isReadOnly: true
-        }, ProjectUser);
+        }, ProjectData);
         this.setState(state)
     }
     editProject(isEdit) {
index 0fe9e96..42e3f18 100644 (file)
@@ -4,6 +4,7 @@
     "dashboard": "./dashboard/dashboard.jsx",
     "order": 1,
     "priority":1,
+    "allow": ["rw-rbac-platform:super-admin", "rw-rbac-platform:platform-admin", "rw-rbac-platform:platform-oper"],
     "routes": [
     {
         "label": "User Management Dashboard",
@@ -14,7 +15,7 @@
         "label": "Platform Role Management",
         "route": "platform",
         "component": "./platformRoleManagement/platformRoleManagement.jsx",
-        "type": "internal"
+        "type": "external"
     },
       {
         "label": "User Profile",
index 28471b7..09e5c6b 100644 (file)
@@ -10,7 +10,7 @@ import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx'
 import 'style/layout.scss';
 import './userMgmt.scss';
 import {Panel, PanelWrapper} from 'widgets/panel/panel';
-
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
 
 import TextInput from 'widgets/form_controls/textInput.jsx';
 import Input from 'widgets/form_controls/input.jsx';
@@ -20,13 +20,15 @@ import 'widgets/form_controls/formControls.scss';
 import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
 import imgRemove from '../../node_modules/open-iconic/svg/trash.svg';
 
+import ROLES from 'utils/roleConstants.js';
+const PLATFORM = ROLES.PLATFORM;
+
 class UserManagementDashboard extends React.Component {
     constructor(props) {
         super(props);
         this.Store = this.props.flux.stores.hasOwnProperty('UserManagementStore') ? this.props.flux.stores.UserManagementStore : this.props.flux.createStore(UserManagementStore);
        this.state = this.Store.getState();
        this.actions = this.state.actions;
-
     }
     componentDidUpdate() {
         let self = this;
@@ -212,10 +214,11 @@ class UserManagementDashboard extends React.Component {
                                 )
                             )
         }
-
         html = (
             <PanelWrapper column>
-                <AppHeader nav={[{name: 'PLATFORM ROLE MANAGEMENT', onClick: this.context.router.push.bind(this, {pathname: '/platform'})}]}/>
+                <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} >
+                    <AppHeader nav={[{name: 'PLATFORM ROLE MANAGEMENT', onClick: this.context.router.push.bind(this, {pathname: '/platform'})}]}/>
+                </SkyquakeRBAC>
                 <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{'flexDirection': 'row'}} >
                     <PanelWrapper ref={(div) => { this.UserList = div}} className={`column userList expanded ${this.state.userOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
                         <Panel title="User List" style={{marginBottom: 0}} no-corners>
@@ -250,9 +253,11 @@ class UserManagementDashboard extends React.Component {
                                 )
                             })}
                         </Panel>
-                        <ButtonGroup  className="buttonGroup" style={{margin: '0 0.5rem 0.5rem', background: '#ddd', paddingBottom: '0.5rem'}}>
-                            <Button label="Add User" onClick={this.addUser} />
+                        <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
+                            <ButtonGroup  className="buttonGroup">
+                                <Button label="Add User" onClick={this.addUser} />
                         </ButtonGroup>
+                        </SkyquakeRBAC>
                     </PanelWrapper>
                     <PanelWrapper onKeyUp={this.evaluateSubmit}
                         className={`userAdmin column`}>
@@ -291,7 +296,9 @@ class UserManagementDashboard extends React.Component {
                             {passwordSectionHTML}
 
                         </Panel>
+                        <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
                             {formButtonsHTML}
+                        </SkyquakeRBAC>
                     </PanelWrapper>
                 </PanelWrapper>
             </PanelWrapper>
index 79511b0..da31abf 100644 (file)
 
         }
     }
-    .buttonGroup {
+    .rbacButtonGroup {
         margin: 0 0.5rem 0.5rem;
         background: #ddd;
         padding-bottom: 0.5rem;
         padding: 0.5rem 0;
+    }
+    .buttonGroup {
         border-top: #d3d3d3 1px solid;
+        padding-top:0.5rem;
     }
     table {
         font-size: 0.8rem;