From 8cf313ec82661ba235990d6073bc308569b3c417 Mon Sep 17 00:00:00 2001 From: Jayasanka Weerasinghe Date: Thu, 16 Apr 2020 18:41:03 +0530 Subject: [PATCH] Add permission based access control to APPM Publisher --- .../react-app/public/conf/config.json | 1 + .../react-app/src/App.js | 65 +++-- .../src/components/Authorized/Authorized.js | 39 +++ .../react-app/src/scenes/Home/index.js | 60 ++--- .../components/NewAppDetailsForm/index.js | 106 +++----- .../components/NewAppUploadForm/index.js | 19 +- .../components/AddNewAppForm/index.js | 17 +- .../scenes/AddNewApp/scenes/Custom/index.js | 15 +- .../AddNewApp/scenes/Enterprise/index.js | 15 +- .../scenes/AddNewApp/scenes/Public/index.js | 15 +- .../scenes/AddNewApp/scenes/WebClip/index.js | 15 +- .../ApssTable/AppDetailsDrawer/index.js | 208 ++++++++++------ .../AppList/components/Filters/index.js | 226 +++++++----------- .../src/scenes/Home/scenes/Apps/index.js | 14 +- .../components/EditRelease/index.js | 19 +- .../ReleaseView/components/Reviews/index.js | 10 +- .../Release/components/ReleaseView/index.js | 132 ++++++---- .../Home/scenes/Apps/scenes/Release/index.js | 86 +++---- .../Manage/components/Categories/index.js | 91 ++++--- .../scenes/Manage/components/Tags/index.js | 91 ++++--- .../src/scenes/Home/scenes/Manage/index.js | 20 +- .../components/Pages/index.js | 65 ++--- .../Manage/scenes/AndroidEnterprise/index.js | 28 ++- .../scenes/Page/components/Cluster/index.js | 82 ++++--- .../AndroidEnterprise/scenes/Page/index.js | 206 +++++++++------- .../services/utils/authorizationHandler.js | 24 ++ 26 files changed, 936 insertions(+), 733 deletions(-) create mode 100644 components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/Authorized/Authorized.js create mode 100644 components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/services/utils/authorizationHandler.js diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json index 9d78b67906..e6c00f0849 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json @@ -6,6 +6,7 @@ }, "serverConfig": { "invoker": { + "contextPath" : "/publisher-ui-request-handler", "uri": "/publisher-ui-request-handler/invoke", "publisher": "/application-mgt-publisher/v1.0", "store": "/application-mgt-store/v1.0", diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js index 177d362c3e..78a387b656 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js @@ -106,37 +106,24 @@ class App extends React.Component { checkUserLoggedIn = config => { axios .post( - window.location.origin + '/publisher-ui-request-handler/user', - 'platform=publisher', + window.location.origin + + config.serverConfig.invoker.contextPath + + '/user', ) .then(res => { - config.user = res.data.data; + config.user = { + username: res.data.data, + }; const pageURL = window.location.pathname; const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); if (lastURLSegment === 'login') { window.location.href = window.location.origin + '/publisher/'; } else { - this.getAndroidEnterpriseToken(config); + this.getUserPermissions(config); } }) .catch(error => { - if (error.hasOwnProperty('response') && error.response.status === 401) { - const redirectUrl = encodeURI(window.location.href); - const pageURL = window.location.pathname; - const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); - if (lastURLSegment !== 'login') { - window.location.href = - window.location.origin + - `/publisher/login?redirect=${redirectUrl}`; - } else { - this.getAndroidEnterpriseToken(config); - } - } else { - this.setState({ - loading: false, - error: true, - }); - } + this.handleApiError(error, config); }); }; @@ -152,6 +139,42 @@ class App extends React.Component { document.getElementsByTagName('head')[0].appendChild(link); }; + getUserPermissions = config => { + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/users/current-user/permissions', + ) + .then(res => { + config.user.permissions = res.data.data.permissions; + this.getAndroidEnterpriseToken(config); + }) + .catch(error => { + this.handleApiError(error, config); + }); + }; + + handleApiError = (error, config) => { + if (error.hasOwnProperty('response') && error.response.status === 401) { + const redirectUrl = encodeURI(window.location.href); + const pageURL = window.location.pathname; + const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); + if (lastURLSegment !== 'login') { + window.location.href = + window.location.origin + `/publisher/login?redirect=${redirectUrl}`; + } else { + this.getAndroidEnterpriseToken(config); + } + } else { + this.setState({ + loading: false, + error: true, + }); + } + }; + render() { const { loading, error } = this.state; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/Authorized/Authorized.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/Authorized/Authorized.js new file mode 100644 index 0000000000..7f06a0403c --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/Authorized/Authorized.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. + * + * Entgra (pvt) Ltd. licenses this file to you 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 { withConfigContext } from '../ConfigContext'; +import { isAuthorized } from '../../services/utils/authorizationHandler'; + +class Authorized extends react.Component { + constructor(props) { + super(props); + } + + render() { + return isAuthorized(this.props.context.user, this.props.permission) + ? this.props.yes + : this.props.no; + } +} + +Authorized.defaultProps = { + yes: null, + no: null, +}; +export default withConfigContext(Authorized); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/index.js index c21ade6196..983e74c07b 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/index.js @@ -24,6 +24,7 @@ import { Redirect } from 'react-router'; import './styles.css'; import { withConfigContext } from '../../components/ConfigContext'; import Logout from './components/Logout'; +import { isAuthorized } from '../../services/utils/authorizationHandler'; const { Header, Content, Footer } = Layout; const { SubMenu } = Menu; @@ -84,33 +85,36 @@ class Dashboard extends React.Component { Apps - - - - Add New App - - } - > - - Public App - - - - Enterprise App - - - - Web Clip - - - - Custom App - - - - + {isAuthorized( + this.props.context.user, + '/permission/admin/app-mgt/publisher/application/update', + ) && ( + + + Add New App + + } + > + + Public App + + + + Enterprise App + + + + Web Clip + + + + Custom App + + + + )} @@ -139,7 +143,7 @@ class Dashboard extends React.Component { title={ - {this.config.user} + {this.config.username} } > diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppDetailsForm/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppDetailsForm/index.js index f4e4bababa..c5f6c5389d 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppDetailsForm/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppDetailsForm/index.js @@ -22,6 +22,7 @@ import axios from 'axios'; import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import { handleApiError } from '../../../../../../../../services/utils/errorHandler'; import debounce from 'lodash.debounce'; +import Authorized from '../../../../../../../../components/Authorized/Authorized'; const formItemLayout = { labelCol: { @@ -46,12 +47,6 @@ class NewAppDetailsForm extends React.Component { fetching: false, roleSearchValue: [], unrestrictedRoles: [], - forbiddenErrors: { - categories: false, - tags: false, - deviceTypes: false, - roles: false, - }, }; this.lastFetchId = 0; this.fetchRoles = debounce(this.fetchRoles, 800); @@ -127,18 +122,9 @@ class NewAppDetailsForm extends React.Component { 'Error occurred while trying to load categories.', true, ); - if (error.hasOwnProperty('response') && error.response.status === 403) { - const { forbiddenErrors } = this.state; - forbiddenErrors.categories = true; - this.setState({ - forbiddenErrors, - loading: false, - }); - } else { - this.setState({ - loading: false, - }); - } + this.setState({ + loading: false, + }); }); }; @@ -166,18 +152,9 @@ class NewAppDetailsForm extends React.Component { 'Error occurred while trying to load tags.', true, ); - if (error.hasOwnProperty('response') && error.response.status === 403) { - const { forbiddenErrors } = this.state; - forbiddenErrors.tags = true; - this.setState({ - forbiddenErrors, - loading: false, - }); - } else { - this.setState({ - loading: false, - }); - } + this.setState({ + loading: false, + }); }); }; @@ -279,18 +256,9 @@ class NewAppDetailsForm extends React.Component { 'Error occurred while trying to load roles.', true, ); - if (error.hasOwnProperty('response') && error.response.status === 403) { - const { forbiddenErrors } = this.state; - forbiddenErrors.roles = true; - this.setState({ - forbiddenErrors, - fetching: false, - }); - } else { - this.setState({ - fetching: false, - }); - } + this.setState({ + fetching: false, + }); }); }; @@ -310,7 +278,6 @@ class NewAppDetailsForm extends React.Component { deviceTypes, fetching, unrestrictedRoles, - forbiddenErrors, } = this.state; const { getFieldDecorator } = this.props.form; @@ -326,14 +293,17 @@ class NewAppDetailsForm extends React.Component { > {formConfig.installationType !== 'WEB_CLIP' && (
- {forbiddenErrors.deviceTypes && ( - - )} + + } + /> {getFieldDecorator('deviceType', { rules: [ @@ -387,14 +357,16 @@ class NewAppDetailsForm extends React.Component { {/* Unrestricted Roles*/} - {forbiddenErrors.roles && ( - - )} + + } + /> {getFieldDecorator('unrestrictedRoles', { rules: [], @@ -417,14 +389,6 @@ class NewAppDetailsForm extends React.Component { , )} - {forbiddenErrors.categories && ( - - )} {getFieldDecorator('categories', { rules: [ @@ -450,14 +414,6 @@ class NewAppDetailsForm extends React.Component { , )} - {forbiddenErrors.tags && ( - - )} {getFieldDecorator('tags', { rules: [ diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppUploadForm/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppUploadForm/index.js index 6e1f0ec7de..1073adbd78 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppUploadForm/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/components/AddNewAppForm/components/NewAppUploadForm/index.js @@ -31,6 +31,7 @@ import { Alert, } from 'antd'; import '@babel/polyfill'; +import Authorized from '../../../../../../../../components/Authorized/Authorized'; const { Text } = Typography; @@ -466,14 +467,16 @@ class NewAppUploadForm extends React.Component { {formConfig.installationType !== 'WEB_CLIP' && formConfig.installationType !== 'CUSTOM' && (
- {this.props.forbiddenErrors.supportedOsVersions && ( - - )} + + } + />
- + } + no={ + + } + />
); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Enterprise/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Enterprise/index.js index e870daa7a1..0e199439d8 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Enterprise/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Enterprise/index.js @@ -17,9 +17,10 @@ */ import React from 'react'; -import { PageHeader, Typography, Breadcrumb, Icon } from 'antd'; +import { PageHeader, Typography, Breadcrumb, Icon, Result } from 'antd'; import AddNewAppForm from '../../components/AddNewAppForm'; import { Link } from 'react-router-dom'; +import Authorized from '../../../../../../components/Authorized/Authorized'; const { Paragraph } = Typography; @@ -64,7 +65,17 @@ class AddNewEnterpriseApp extends React.Component {
- + } + no={ + + } + />
); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Public/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Public/index.js index 349089fba7..987da9a2ce 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Public/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/Public/index.js @@ -17,9 +17,10 @@ */ import React from 'react'; -import { Icon, PageHeader, Typography, Breadcrumb } from 'antd'; +import { Icon, PageHeader, Typography, Breadcrumb, Result } from 'antd'; import AddNewAppForm from '../../components/AddNewAppForm'; import { Link } from 'react-router-dom'; +import Authorized from '../../../../../../components/Authorized/Authorized'; const { Paragraph } = Typography; @@ -72,7 +73,17 @@ class AddNewPublicApp extends React.Component {
- + } + no={ + + } + />
); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/WebClip/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/WebClip/index.js index 388d6a4d14..c002c68756 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/WebClip/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/AddNewApp/scenes/WebClip/index.js @@ -17,9 +17,10 @@ */ import React from 'react'; -import { Icon, PageHeader, Typography, Breadcrumb } from 'antd'; +import { Icon, PageHeader, Typography, Breadcrumb, Result } from 'antd'; import AddNewAppForm from '../../components/AddNewAppForm'; import { Link } from 'react-router-dom'; +import Authorized from '../../../../../../components/Authorized/Authorized'; const { Paragraph } = Typography; @@ -65,7 +66,17 @@ class AddNewEnterpriseApp extends React.Component {
- + } + no={ + + } + />
); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/Apps/components/AppList/components/ApssTable/AppDetailsDrawer/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/Apps/components/AppList/components/ApssTable/AppDetailsDrawer/index.js index 41fc9f10d9..277b5ada8a 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/Apps/components/AppList/components/ApssTable/AppDetailsDrawer/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/scenes/Apps/components/AppList/components/ApssTable/AppDetailsDrawer/index.js @@ -44,6 +44,8 @@ import pSBC from 'shade-blend-color'; import { withConfigContext } from '../../../../../../../../../components/ConfigContext'; import ManagedConfigurationsIframe from './components/ManagedConfigurationsIframe'; import { handleApiError } from '../../../../../../../../../services/utils/errorHandler'; +import Authorized from '../../../../../../../../../components/Authorized/Authorized'; +import { isAuthorized } from '../../../../../../../../../services/utils/authorizationHandler'; const { Meta } = Card; const { Text, Title } = Typography; @@ -100,7 +102,15 @@ class AppDetailsDrawer extends React.Component { } componentDidMount() { - this.getCategories(); + if ( + isAuthorized( + this.props.context.user, + '/permission/admin/app-mgt/publisher/application/update', + ) + ) { + this.getCategories(); + this.getTags(); + } } componentDidUpdate(prevProps, prevState, snapshot) { @@ -130,7 +140,6 @@ class AppDetailsDrawer extends React.Component { .then(res => { if (res.status === 200) { const categories = JSON.parse(res.data.data); - this.getTags(); const globalCategories = categories.map(category => { return (