Merge branch 'feature/appm-publisher/pbac' into 'master'

Add permission based access control to APPM Publisher

Closes product-iots#483

See merge request entgra/carbon-device-mgt!507
reporting
Dharmakeerthi Lasantha 5 years ago
commit 831b2fa89f

@ -6,6 +6,7 @@
}, },
"serverConfig": { "serverConfig": {
"invoker": { "invoker": {
"contextPath" : "/publisher-ui-request-handler",
"uri": "/publisher-ui-request-handler/invoke", "uri": "/publisher-ui-request-handler/invoke",
"publisher": "/application-mgt-publisher/v1.0", "publisher": "/application-mgt-publisher/v1.0",
"store": "/application-mgt-store/v1.0", "store": "/application-mgt-store/v1.0",

@ -106,37 +106,24 @@ class App extends React.Component {
checkUserLoggedIn = config => { checkUserLoggedIn = config => {
axios axios
.post( .post(
window.location.origin + '/publisher-ui-request-handler/user', window.location.origin +
'platform=publisher', config.serverConfig.invoker.contextPath +
'/user',
) )
.then(res => { .then(res => {
config.user = res.data.data; config.user = {
username: res.data.data,
};
const pageURL = window.location.pathname; const pageURL = window.location.pathname;
const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1);
if (lastURLSegment === 'login') { if (lastURLSegment === 'login') {
window.location.href = window.location.origin + '/publisher/'; window.location.href = window.location.origin + '/publisher/';
} else { } else {
this.getAndroidEnterpriseToken(config); this.getUserPermissions(config);
} }
}) })
.catch(error => { .catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) { this.handleApiError(error, config);
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,
});
}
}); });
}; };
@ -152,6 +139,42 @@ class App extends React.Component {
document.getElementsByTagName('head')[0].appendChild(link); 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() { render() {
const { loading, error } = this.state; const { loading, error } = this.state;

@ -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);

@ -24,6 +24,7 @@ import { Redirect } from 'react-router';
import './styles.css'; import './styles.css';
import { withConfigContext } from '../../components/ConfigContext'; import { withConfigContext } from '../../components/ConfigContext';
import Logout from './components/Logout'; import Logout from './components/Logout';
import { isAuthorized } from '../../services/utils/authorizationHandler';
const { Header, Content, Footer } = Layout; const { Header, Content, Footer } = Layout;
const { SubMenu } = Menu; const { SubMenu } = Menu;
@ -84,33 +85,36 @@ class Dashboard extends React.Component {
Apps Apps
</Link> </Link>
</Menu.Item> </Menu.Item>
{isAuthorized(
<SubMenu this.props.context.user,
title={ '/permission/admin/app-mgt/publisher/application/update',
<span className="submenu-title-wrapper"> ) && (
<Icon type="plus" /> <SubMenu
Add New App title={
</span> <span className="submenu-title-wrapper">
} <Icon type="plus" />
> Add New App
<Menu.Item key="add-new-public-app"> </span>
<Link to="/publisher/add-new-app/public">Public App</Link> }
</Menu.Item> >
<Menu.Item key="add-new-enterprise-app"> <Menu.Item key="add-new-public-app">
<Link to="/publisher/add-new-app/enterprise"> <Link to="/publisher/add-new-app/public">Public App</Link>
Enterprise App </Menu.Item>
</Link> <Menu.Item key="add-new-enterprise-app">
</Menu.Item> <Link to="/publisher/add-new-app/enterprise">
<Menu.Item key="add-new-web-clip"> Enterprise App
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="add-new-custom-app"> <Menu.Item key="add-new-web-clip">
<Link to="/publisher/add-new-app/custom-app"> <Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
Custom App </Menu.Item>
</Link> <Menu.Item key="add-new-custom-app">
</Menu.Item> <Link to="/publisher/add-new-app/custom-app">
</SubMenu> Custom App
</Link>
</Menu.Item>
</SubMenu>
)}
<SubMenu <SubMenu
title={ title={
<span className="submenu-title-wrapper"> <span className="submenu-title-wrapper">
@ -139,7 +143,7 @@ class Dashboard extends React.Component {
title={ title={
<span className="submenu-title-wrapper"> <span className="submenu-title-wrapper">
<Icon type="user" /> <Icon type="user" />
{this.config.user} {this.config.username}
</span> </span>
} }
> >

@ -22,6 +22,7 @@ import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
@ -46,12 +47,6 @@ class NewAppDetailsForm extends React.Component {
fetching: false, fetching: false,
roleSearchValue: [], roleSearchValue: [],
unrestrictedRoles: [], unrestrictedRoles: [],
forbiddenErrors: {
categories: false,
tags: false,
deviceTypes: false,
roles: false,
},
}; };
this.lastFetchId = 0; this.lastFetchId = 0;
this.fetchRoles = debounce(this.fetchRoles, 800); this.fetchRoles = debounce(this.fetchRoles, 800);
@ -127,18 +122,9 @@ class NewAppDetailsForm extends React.Component {
'Error occurred while trying to load categories.', 'Error occurred while trying to load categories.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.categories = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
@ -166,18 +152,9 @@ class NewAppDetailsForm extends React.Component {
'Error occurred while trying to load tags.', 'Error occurred while trying to load tags.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.tags = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
@ -279,18 +256,9 @@ class NewAppDetailsForm extends React.Component {
'Error occurred while trying to load roles.', 'Error occurred while trying to load roles.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; fetching: false,
forbiddenErrors.roles = true; });
this.setState({
forbiddenErrors,
fetching: false,
});
} else {
this.setState({
fetching: false,
});
}
}); });
}; };
@ -310,7 +278,6 @@ class NewAppDetailsForm extends React.Component {
deviceTypes, deviceTypes,
fetching, fetching,
unrestrictedRoles, unrestrictedRoles,
forbiddenErrors,
} = this.state; } = this.state;
const { getFieldDecorator } = this.props.form; const { getFieldDecorator } = this.props.form;
@ -326,14 +293,17 @@ class NewAppDetailsForm extends React.Component {
> >
{formConfig.installationType !== 'WEB_CLIP' && ( {formConfig.installationType !== 'WEB_CLIP' && (
<div> <div>
{forbiddenErrors.deviceTypes && ( <Authorized
<Alert permission="/permission/admin/device-mgt/admin/device-type/view"
message="You don't have permission to view device types." no={
type="warning" <Alert
banner message="You don't have permission to view device types."
closable type="warning"
/> banner
)} closable
/>
}
/>
<Form.Item {...formItemLayout} label="Device Type"> <Form.Item {...formItemLayout} label="Device Type">
{getFieldDecorator('deviceType', { {getFieldDecorator('deviceType', {
rules: [ rules: [
@ -387,14 +357,16 @@ class NewAppDetailsForm extends React.Component {
</Form.Item> </Form.Item>
{/* Unrestricted Roles*/} {/* Unrestricted Roles*/}
{forbiddenErrors.roles && ( <Authorized
<Alert permission="/permission/admin/device-mgt/roles/view"
message="You don't have permission to view roles." no={
type="warning" <Alert
banner message="You don't have permission to view roles."
closable type="warning"
/> banner
)} />
}
/>
<Form.Item {...formItemLayout} label="Visible Roles"> <Form.Item {...formItemLayout} label="Visible Roles">
{getFieldDecorator('unrestrictedRoles', { {getFieldDecorator('unrestrictedRoles', {
rules: [], rules: [],
@ -417,14 +389,6 @@ class NewAppDetailsForm extends React.Component {
</Select>, </Select>,
)} )}
</Form.Item> </Form.Item>
{forbiddenErrors.categories && (
<Alert
message="You don't have permission to view categories."
type="warning"
banner
closable
/>
)}
<Form.Item {...formItemLayout} label="Categories"> <Form.Item {...formItemLayout} label="Categories">
{getFieldDecorator('categories', { {getFieldDecorator('categories', {
rules: [ rules: [
@ -450,14 +414,6 @@ class NewAppDetailsForm extends React.Component {
</Select>, </Select>,
)} )}
</Form.Item> </Form.Item>
{forbiddenErrors.tags && (
<Alert
message="You don't have permission to view tags."
type="warning"
banner
closable
/>
)}
<Form.Item {...formItemLayout} label="Tags"> <Form.Item {...formItemLayout} label="Tags">
{getFieldDecorator('tags', { {getFieldDecorator('tags', {
rules: [ rules: [

@ -31,6 +31,7 @@ import {
Alert, Alert,
} from 'antd'; } from 'antd';
import '@babel/polyfill'; import '@babel/polyfill';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
const { Text } = Typography; const { Text } = Typography;
@ -466,14 +467,16 @@ class NewAppUploadForm extends React.Component {
{formConfig.installationType !== 'WEB_CLIP' && {formConfig.installationType !== 'WEB_CLIP' &&
formConfig.installationType !== 'CUSTOM' && ( formConfig.installationType !== 'CUSTOM' && (
<div> <div>
{this.props.forbiddenErrors.supportedOsVersions && ( <Authorized
<Alert permission="/permission/admin/device-mgt/admin/device-type"
message="You don't have permission to view supported OS versions." no={
type="warning" <Alert
banner message="You don't have permission to view supported OS versions."
closable type="warning"
/> banner
)} />
}
/>
<Form.Item <Form.Item
{...formItemLayout} {...formItemLayout}
label="Supported OS Versions" label="Supported OS Versions"

@ -149,18 +149,9 @@ class AddNewAppFormComponent extends React.Component {
'Error occurred while trying to load supported OS versions.', 'Error occurred while trying to load supported OS versions.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.supportedOsVersions = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
@ -171,7 +162,6 @@ class AddNewAppFormComponent extends React.Component {
isError, isError,
supportedOsVersions, supportedOsVersions,
errorText, errorText,
forbiddenErrors,
} = this.state; } = this.state;
const { formConfig } = this.props; const { formConfig } = this.props;
return ( return (
@ -193,7 +183,6 @@ class AddNewAppFormComponent extends React.Component {
</div> </div>
<div style={{ display: current === 1 ? 'unset' : 'none' }}> <div style={{ display: current === 1 ? 'unset' : 'none' }}>
<NewAppUploadForm <NewAppUploadForm
forbiddenErrors={forbiddenErrors}
formConfig={formConfig} formConfig={formConfig}
supportedOsVersions={supportedOsVersions} supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData} onSuccessReleaseData={this.onSuccessReleaseData}

@ -17,9 +17,10 @@
*/ */
import React from 'react'; 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 AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography; const { Paragraph } = Typography;
@ -70,7 +71,17 @@ class AddNewCustomApp extends React.Component {
</div> </div>
</PageHeader> </PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} /> <Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
</div> </div>
); );

@ -17,9 +17,10 @@
*/ */
import React from 'react'; 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 AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography; const { Paragraph } = Typography;
@ -64,7 +65,17 @@ class AddNewEnterpriseApp extends React.Component {
</div> </div>
</PageHeader> </PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} /> <Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
</div> </div>
); );

@ -17,9 +17,10 @@
*/ */
import React from 'react'; 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 AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography; const { Paragraph } = Typography;
@ -72,7 +73,17 @@ class AddNewPublicApp extends React.Component {
</div> </div>
</PageHeader> </PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} /> <Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
</div> </div>
); );

@ -17,9 +17,10 @@
*/ */
import React from 'react'; 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 AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
const { Paragraph } = Typography; const { Paragraph } = Typography;
@ -65,7 +66,17 @@ class AddNewEnterpriseApp extends React.Component {
</div> </div>
</PageHeader> </PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<AddNewAppForm formConfig={formConfig} /> <Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={<AddNewAppForm formConfig={formConfig} />}
no={
<Result
status="403"
title="You don't have permission to add new apps."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
</div> </div>
); );

@ -44,6 +44,8 @@ import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../../components/ConfigContext';
import ManagedConfigurationsIframe from './components/ManagedConfigurationsIframe'; import ManagedConfigurationsIframe from './components/ManagedConfigurationsIframe';
import { handleApiError } from '../../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../../../../services/utils/authorizationHandler';
const { Meta } = Card; const { Meta } = Card;
const { Text, Title } = Typography; const { Text, Title } = Typography;
@ -100,7 +102,15 @@ class AppDetailsDrawer extends React.Component {
} }
componentDidMount() { componentDidMount() {
this.getCategories(); if (
isAuthorized(
this.props.context.user,
'/permission/admin/app-mgt/publisher/application/update',
)
) {
this.getCategories();
this.getTags();
}
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
@ -130,7 +140,6 @@ class AppDetailsDrawer extends React.Component {
.then(res => { .then(res => {
if (res.status === 200) { if (res.status === 200) {
const categories = JSON.parse(res.data.data); const categories = JSON.parse(res.data.data);
this.getTags();
const globalCategories = categories.map(category => { const globalCategories = categories.map(category => {
return ( return (
<Option key={category.categoryName}> <Option key={category.categoryName}>
@ -520,36 +529,48 @@ class AppDetailsDrawer extends React.Component {
<Spin spinning={loading} delay={500}> <Spin spinning={loading} delay={500}>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
{avatar} {avatar}
<Title editable={{ onChange: this.handleNameSave }} level={2}> <Authorized
{name} permission="/permission/admin/app-mgt/publisher/application/update"
</Title> yes={
<Title editable={{ onChange: this.handleNameSave }} level={2}>
{name}
</Title>
}
no={<Title level={2}>{name}</Title>}
/>
</div> </div>
<Divider /> <Divider />
{/* display manage config button only if the app is public android app*/} {/* display manage config button only if the app is public android app*/}
{app.isAndroidEnterpriseApp && {app.isAndroidEnterpriseApp &&
config.androidEnterpriseToken !== null && ( config.androidEnterpriseToken !== null && (
<div> <Authorized
<div> permission="/permission/admin/device-mgt/enterprise/user/modify"
<Text strong={true}>Set up managed configurations</Text> yes={
</div> <div>
<div style={{ paddingTop: 16 }}> <div>
<Text> <Text strong={true}>Set up managed configurations</Text>
If you are developing apps for the enterprise market, you </div>
may need to satisfy particular requirements set by a <div style={{ paddingTop: 16 }}>
organization&apos;s policies. Managed configurations, <Text>
previously known as application restrictions, allow the If you are developing apps for the enterprise market,
organization&apos;s IT admin to remotely specify settings you may need to satisfy particular requirements set by
for apps. This capability is particularly useful for a organization&apos;s policies. Managed
organization-approved apps deployed to a work profile. configurations, previously known as application
</Text> restrictions, allow the organization&apos;s IT admin
</div> to remotely specify settings for apps. This capability
<br /> is particularly useful for organization-approved apps
<ManagedConfigurationsIframe deployed to a work profile.
style={{ paddingTop: 16 }} </Text>
packageName={app.packageName} </div>
/> <br />
<Divider dashed={true} /> <ManagedConfigurationsIframe
</div> style={{ paddingTop: 16 }}
packageName={app.packageName}
/>
<Divider dashed={true} />
</div>
}
/>
)} )}
<Text strong={true}>Releases </Text> <Text strong={true}>Releases </Text>
<div className="releases-details"> <div className="releases-details">
@ -640,34 +661,44 @@ class AppDetailsDrawer extends React.Component {
{/* display add new release only if app type is enterprise*/} {/* display add new release only if app type is enterprise*/}
{app.type === 'ENTERPRISE' && ( {app.type === 'ENTERPRISE' && (
<div> <Authorized
<Divider dashed={true} /> permission="/permission/admin/app-mgt/publisher/application/update"
<div style={{ paddingBottom: 16 }}> yes={
<Text>Add new release for the application</Text> <div>
</div> <Divider dashed={true} />
<Link <div style={{ paddingBottom: 16 }}>
to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`} <Text>Add new release for the application</Text>
> </div>
<Button htmlType="button" type="primary" size="small"> <Link
Add to={`/publisher/apps/${app.deviceType}/${app.id}/add-release`}
</Button> >
</Link> <Button htmlType="button" type="primary" size="small">
</div> Add
</Button>
</Link>
</div>
}
/>
)} )}
<Divider dashed={true} /> <Divider dashed={true} />
<Text strong={true}>Description </Text> <Text strong={true}>Description </Text>
{!isDescriptionEditEnabled && ( <Authorized
<Text permission="/permission/admin/app-mgt/publisher/application/update"
style={{ yes={
color: config.theme.primaryColor, !isDescriptionEditEnabled && (
cursor: 'pointer', <Text
}} style={{
onClick={this.enableDescriptionEdit} color: config.theme.primaryColor,
> cursor: 'pointer',
<Icon type="edit" /> }}
</Text> onClick={this.enableDescriptionEdit}
)} >
<Icon type="edit" />
</Text>
)
}
/>
{!isDescriptionEditEnabled && ( {!isDescriptionEditEnabled && (
<div>{ReactHtmlParser(description)}</div> <div>{ReactHtmlParser(description)}</div>
@ -708,14 +739,22 @@ class AppDetailsDrawer extends React.Component {
<Divider dashed={true} /> <Divider dashed={true} />
<Text strong={true}>Categories </Text> <Text strong={true}>Categories </Text>
{!isCategoriesEditEnabled && ( <Authorized
<Text permission="/permission/admin/app-mgt/publisher/application/update"
style={{ color: config.theme.primaryColor, cursor: 'pointer' }} yes={
onClick={this.enableCategoriesEdit} !isCategoriesEditEnabled && (
> <Text
<Icon type="edit" /> style={{
</Text> color: config.theme.primaryColor,
)} cursor: 'pointer',
}}
onClick={this.enableCategoriesEdit}
>
<Icon type="edit" />
</Text>
)
}
/>
<br /> <br />
<br /> <br />
{isCategoriesEditEnabled && ( {isCategoriesEditEnabled && (
@ -767,14 +806,22 @@ class AppDetailsDrawer extends React.Component {
<Divider dashed={true} /> <Divider dashed={true} />
<Text strong={true}>Tags </Text> <Text strong={true}>Tags </Text>
{!isTagsEditEnabled && ( <Authorized
<Text permission="/permission/admin/app-mgt/publisher/application/update"
style={{ color: config.theme.primaryColor, cursor: 'pointer' }} yes={
onClick={this.enableTagsEdit} !isTagsEditEnabled && (
> <Text
<Icon type="edit" /> style={{
</Text> color: config.theme.primaryColor,
)} cursor: 'pointer',
}}
onClick={this.enableTagsEdit}
>
<Icon type="edit" />
</Text>
)
}
/>
<br /> <br />
<br /> <br />
{isTagsEditEnabled && ( {isTagsEditEnabled && (
@ -819,17 +866,22 @@ class AppDetailsDrawer extends React.Component {
})} })}
</span> </span>
)} )}
<Authorized
<Divider dashed={true} /> permission="/permission/admin/app-mgt/publisher/review/view"
yes={
<div className="app-rate"> <div>
{app.applicationReleases.length > 0 && ( <Divider dashed={true} />
<DetailedRating <div className="app-rate">
type="app" {app.applicationReleases.length > 0 && (
uuid={app.applicationReleases[0].uuid} <DetailedRating
/> type="app"
)} uuid={app.applicationReleases[0].uuid}
</div> />
)}
</div>
</div>
}
/>
</Spin> </Spin>
</Drawer> </Drawer>
</div> </div>

@ -26,11 +26,11 @@ import {
Select, Select,
Button, Button,
Form, Form,
Alert,
} from 'antd'; } from 'antd';
import axios from 'axios'; import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
const { Option } = Select; const { Option } = Select;
const { Title } = Typography; const { Title } = Typography;
@ -42,11 +42,6 @@ class FiltersForm extends React.Component {
categories: [], categories: [],
tags: [], tags: [],
deviceTypes: [], deviceTypes: [],
forbiddenErrors: {
categories: false,
tags: false,
deviceTypes: false,
},
}; };
} }
@ -101,18 +96,9 @@ class FiltersForm extends React.Component {
'Error occurred while trying to load categories.', 'Error occurred while trying to load categories.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.categories = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
@ -140,18 +126,9 @@ class FiltersForm extends React.Component {
'Error occurred while trying to load tags.', 'Error occurred while trying to load tags.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.tags = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
@ -179,23 +156,14 @@ class FiltersForm extends React.Component {
'Error occurred while trying to load device types.', 'Error occurred while trying to load device types.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.deviceTypes = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
render() { render() {
const { categories, tags, deviceTypes, forbiddenErrors } = this.state; const { categories, tags, deviceTypes } = this.state;
const { getFieldDecorator } = this.props.form; const { getFieldDecorator } = this.props.form;
return ( return (
@ -224,99 +192,85 @@ class FiltersForm extends React.Component {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
{forbiddenErrors.categories && ( <Authorized
<Alert permission="/permission/admin/app-mgt/publisher/application/update"
message="You don't have permission to view categories." yes={
type="warning" <div>
banner <Form.Item label="Categories">
closable {getFieldDecorator('categories', {
/> rules: [
)} {
<Form.Item label="Categories"> required: false,
{getFieldDecorator('categories', { message: 'Please select categories',
rules: [ },
{ ],
required: false, })(
message: 'Please select categories', <Select
}, mode="multiple"
], style={{ width: '100%' }}
})( placeholder="Select a Category"
<Select onChange={this.handleCategoryChange}
mode="multiple" >
style={{ width: '100%' }} {categories.map(category => {
placeholder="Select a Category" return (
onChange={this.handleCategoryChange} <Option key={category.categoryName}>
> {category.categoryName}
{categories.map(category => { </Option>
return ( );
<Option key={category.categoryName}> })}
{category.categoryName} </Select>,
</Option> )}
); </Form.Item>
})} <Form.Item label="Tags">
</Select>, {getFieldDecorator('tags', {
)} rules: [
</Form.Item> {
required: false,
{forbiddenErrors.deviceTypes && ( message: 'Please select tags',
<Alert },
message="You don't have permission to view device types." ],
type="warning" })(
banner <Select
closable mode="multiple"
/> style={{ width: '100%' }}
)} placeholder="Select tags"
<Form.Item label="Device Type"> >
{getFieldDecorator('deviceType', { {tags.map(tag => {
rules: [ return <Option key={tag.tagName}>{tag.tagName}</Option>;
{ })}
required: false, </Select>,
message: 'Please select device types', )}
}, </Form.Item>
], </div>
})( }
<Select />
style={{ width: '100%' }} <Authorized
placeholder="Select device types" permission="/permission/admin/device-mgt/admin/device-type/view"
> yes={
{deviceTypes.map(deviceType => { <Form.Item label="Device Type">
return ( {getFieldDecorator('deviceType', {
<Option key={deviceType.name}>{deviceType.name}</Option> rules: [
); {
})} required: false,
<Option key="ALL">All</Option> message: 'Please select device types',
</Select>, },
)} ],
</Form.Item> })(
{forbiddenErrors.tags && ( <Select
<Alert style={{ width: '100%' }}
message="You don't have permission to view tags." placeholder="Select device types"
type="warning" >
banner {deviceTypes.map(deviceType => {
closable return (
/> <Option key={deviceType.name}>{deviceType.name}</Option>
)} );
<Form.Item label="Tags"> })}
{getFieldDecorator('tags', { <Option key="ALL">All</Option>
rules: [ </Select>,
{ )}
required: false, </Form.Item>
message: 'Please select tags', }
}, />
],
})(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select tags"
>
{tags.map(tag => {
return <Option key={tag.tagName}>{tag.tagName}</Option>;
})}
</Select>,
)}
</Form.Item>
<Form.Item label="App Type"> <Form.Item label="App Type">
{getFieldDecorator('appType', {})( {getFieldDecorator('appType', {})(
<Select style={{ width: '100%' }} placeholder="Select app type"> <Select style={{ width: '100%' }} placeholder="Select app type">

@ -18,6 +18,8 @@
import React from 'react'; import React from 'react';
import AppList from './components/AppList'; import AppList from './components/AppList';
import Authorized from '../../../../components/Authorized/Authorized';
import { Result } from 'antd';
class Apps extends React.Component { class Apps extends React.Component {
routes; routes;
@ -30,7 +32,17 @@ class Apps extends React.Component {
return ( return (
<div> <div>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
<AppList /> <Authorized
permission="/permission/admin/app-mgt/publisher/application/view"
yes={<AppList />}
no={
<Result
status="403"
title="You don't have permission to view apps."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
</div> </div>
); );

@ -36,6 +36,7 @@ import {
import axios from 'axios'; import axios from 'axios';
import '@babel/polyfill'; import '@babel/polyfill';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
import Authorized from '../../../../../../../../../../components/Authorized/Authorized';
const { TextArea } = Input; const { TextArea } = Input;
const InputGroup = Input.Group; const InputGroup = Input.Group;
@ -549,14 +550,16 @@ class EditReleaseModal extends React.Component {
</Form.Item> </Form.Item>
{config.deviceTypes.mobileTypes.includes(deviceType) && ( {config.deviceTypes.mobileTypes.includes(deviceType) && (
<div> <div>
{this.props.forbiddenErrors.supportedOsVersions && ( <Authorized
<Alert permission="/permission/admin/device-mgt/admin/device-type"
message="You don't have permission to view supported OS versions." no={
type="warning" <Alert
banner message="You don't have permission to view supported OS versions."
closable type="warning"
/> banner
)} />
}
/>
<Form.Item <Form.Item
{...formItemLayout} {...formItemLayout}
label="Supported OS Versions" label="Supported OS Versions"

@ -17,7 +17,7 @@
*/ */
import React from 'react'; import React from 'react';
import { List, Spin, Button, Alert } from 'antd'; import { List, Spin, Button } from 'antd';
import './styles.css'; import './styles.css';
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
@ -130,14 +130,6 @@ class Reviews extends React.Component {
render() { render() {
return ( return (
<div> <div>
{this.state.forbiddenErrors.reviews && (
<Alert
message="You don't have permission to view reviews."
type="warning"
banner
closable
/>
)}
<div className="demo-infinite-container"> <div className="demo-infinite-container">
<InfiniteScroll <InfiniteScroll
initialLoad={false} initialLoad={false}

@ -17,13 +17,23 @@
*/ */
import React from 'react'; import React from 'react';
import { Divider, Row, Col, Typography, Button, Icon, Tooltip } from 'antd'; import {
Divider,
Row,
Col,
Typography,
Button,
Icon,
Tooltip,
Alert,
} from 'antd';
import StarRatings from 'react-star-ratings'; import StarRatings from 'react-star-ratings';
import Reviews from './components/Reviews'; import Reviews from './components/Reviews';
import '../../../../../../../../App.css'; import '../../../../../../../../App.css';
import DetailedRating from '../../../../components/DetailedRating'; import DetailedRating from '../../../../components/DetailedRating';
import EditRelease from './components/EditRelease'; import EditRelease from './components/EditRelease';
import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
const { Title, Text, Paragraph } = Typography; const { Title, Text, Paragraph } = Typography;
@ -33,22 +43,19 @@ class ReleaseView extends React.Component {
this.state = {}; this.state = {};
} }
componentDidMount() {
console.log('mounted: Release view');
}
render() { render() {
const { app, release } = this.props; const { app, release } = this.props;
const config = this.props.context; const config = this.props.context;
const { lifecycle, currentLifecycleStatus } = this.props; const { lifecycle, currentLifecycleStatus } = this.props;
if (release == null) {
if (release == null || lifecycle == null) {
return null; return null;
} }
let isAppUpdatable,
const { isAppUpdatable, isAppInstallable } = lifecycle[ isAppInstallable = false;
currentLifecycleStatus if (lifecycle != null) {
]; isAppUpdatable = lifecycle[currentLifecycleStatus].isAppUpdatable;
isAppInstallable = lifecycle[currentLifecycleStatus].isAppInstallable;
}
const platform = app.deviceType; const platform = app.deviceType;
const defaultPlatformIcons = config.defaultPlatformIcons; const defaultPlatformIcons = config.defaultPlatformIcons;
@ -93,45 +100,53 @@ class ReleaseView extends React.Component {
<Divider type="vertical" /> <Divider type="vertical" />
<Text>Version : {release.version}</Text> <Text>Version : {release.version}</Text>
<br /> <br />
<Authorized
<EditRelease permission="/permission/admin/app-mgt/publisher/application/update"
forbiddenErrors={this.props.forbiddenErrors} yes={
isAppUpdatable={isAppUpdatable} <EditRelease
type={app.type} isAppUpdatable={isAppUpdatable}
deviceType={app.deviceType} type={app.type}
release={release} deviceType={app.deviceType}
updateRelease={this.props.updateRelease} release={release}
supportedOsVersions={[...this.props.supportedOsVersions]} updateRelease={this.props.updateRelease}
supportedOsVersions={[...this.props.supportedOsVersions]}
/>
}
/> />
</Col> </Col>
<Col xl={8} md={10} sm={24} xs={24} style={{ float: 'right' }}> <Col xl={8} md={10} sm={24} xs={24} style={{ float: 'right' }}>
<div> <div>
<Tooltip <Authorized
title={ permission="/permission/admin/app-mgt/publisher/application/update"
isAppInstallable yes={
? 'Open this app in store' <Tooltip
: "This release isn't in an installable state" title={
isAppInstallable
? 'Open this app in store'
: "This release isn't in an installable state"
}
>
<Button
style={{ float: 'right' }}
htmlType="button"
type="primary"
icon="shop"
disabled={!isAppInstallable}
onClick={() => {
window.open(
window.location.origin +
'/store/' +
app.deviceType +
'/apps/' +
release.uuid,
);
}}
>
Open in store
</Button>
</Tooltip>
} }
> />
<Button
style={{ float: 'right' }}
htmlType="button"
type="primary"
icon="shop"
disabled={!isAppInstallable}
onClick={() => {
window.open(
window.location.origin +
'/store/' +
app.deviceType +
'/apps/' +
release.uuid,
);
}}
>
Open in store
</Button>
</Tooltip>
</div> </div>
</Col> </Col>
</Row> </Row>
@ -173,12 +188,27 @@ class ReleaseView extends React.Component {
</Row> </Row>
<Divider /> <Divider />
<Text>REVIEWS</Text> <Text>REVIEWS</Text>
<Row> <Authorized
<Col lg={18}> permission="/permission/admin/app-mgt/publisher/admin/review/view"
<DetailedRating type="release" uuid={release.uuid} /> yes={
</Col> <div>
</Row> <Row>
<Reviews type="release" uuid={release.uuid} /> <Col lg={18}>
<DetailedRating type="release" uuid={release.uuid} />
</Col>
</Row>
<Reviews type="release" uuid={release.uuid} />
</div>
}
no={
<Alert
message="You don't have permission to view reviews."
type="warning"
banner
closable
/>
}
/>
</div> </div>
</div> </div>
); );

@ -24,6 +24,8 @@ import ReleaseView from './components/ReleaseView';
import LifeCycle from './components/LifeCycle'; import LifeCycle from './components/LifeCycle';
import { withConfigContext } from '../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../services/utils/authorizationHandler';
const { Title } = Typography; const { Title } = Typography;
@ -41,17 +43,20 @@ class Release extends React.Component {
currentLifecycleStatus: null, currentLifecycleStatus: null,
lifecycle: null, lifecycle: null,
supportedOsVersions: [], supportedOsVersions: [],
forbiddenErrors: {
supportedOsVersions: false,
lifeCycle: false,
},
}; };
} }
componentDidMount() { componentDidMount() {
const { uuid } = this.props.match.params; const { uuid } = this.props.match.params;
this.fetchData(uuid); this.fetchData(uuid);
this.getLifecycle(); if (
isAuthorized(
this.props.context.user,
'/permission/admin/app-mgt/publisher/application/update',
)
) {
this.getLifecycle();
}
} }
changeCurrentLifecycleStatus = status => { changeCurrentLifecycleStatus = status => {
@ -92,7 +97,16 @@ class Release extends React.Component {
uuid: uuid, uuid: uuid,
}); });
if (config.deviceTypes.mobileTypes.includes(app.deviceType)) { if (config.deviceTypes.mobileTypes.includes(app.deviceType)) {
this.getSupportedOsVersions(app.deviceType); if (
isAuthorized(
config.user,
'/permission/admin/device-mgt/admin/device-type',
)
) {
this.getSupportedOsVersions(app.deviceType);
} else {
this.setState({ loading: false });
}
} }
} }
}) })
@ -128,13 +142,6 @@ class Release extends React.Component {
'Error occurred while trying to load lifecycle configuration.', 'Error occurred while trying to load lifecycle configuration.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) {
const { forbiddenErrors } = this.state;
forbiddenErrors.lifeCycle = true;
this.setState({
forbiddenErrors,
});
}
}); });
}; };
@ -161,18 +168,9 @@ class Release extends React.Component {
'Error occurred while trying to load supported OS versions.', 'Error occurred while trying to load supported OS versions.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.supportedOsVersions = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
}; };
@ -193,7 +191,6 @@ class Release extends React.Component {
</div> </div>
); );
} }
// todo remove uppercase // todo remove uppercase
return ( return (
<div> <div>
@ -221,22 +218,27 @@ class Release extends React.Component {
</Skeleton> </Skeleton>
</Card> </Card>
</Col> </Col>
<Col lg={8} md={24} style={{ padding: 3 }}> <Authorized
<Card lg={8} md={24}> permission="/permission/admin/app-mgt/publisher/application/update"
<Skeleton loading={loading} active paragraph={{ rows: 8 }}> yes={
{release !== null && ( <Col lg={8} md={24} style={{ padding: 3 }}>
<LifeCycle <Card lg={8} md={24}>
uuid={release.uuid} <Skeleton loading={loading} active paragraph={{ rows: 8 }}>
currentStatus={release.currentStatus.toUpperCase()} {release !== null && (
changeCurrentLifecycleStatus={ <LifeCycle
this.changeCurrentLifecycleStatus uuid={release.uuid}
} currentStatus={release.currentStatus.toUpperCase()}
lifecycle={lifecycle} changeCurrentLifecycleStatus={
/> this.changeCurrentLifecycleStatus
)} }
</Skeleton> lifecycle={lifecycle}
</Card> />
</Col> )}
</Skeleton>
</Card>
</Col>
}
/>
</Row> </Row>
</div> </div>
</div> </div>

@ -40,6 +40,7 @@ import { TweenOneGroup } from 'rc-tween-one';
import pSBC from 'shade-blend-color'; import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../services/utils/errorHandler';
import { isAuthorized } from '../../../../../../services/utils/authorizationHandler';
const { Title } = Typography; const { Title } = Typography;
@ -62,6 +63,10 @@ class ManageCategories extends React.Component {
componentDidMount() { componentDidMount() {
const config = this.props.context; const config = this.props.context;
this.hasPermissionToManage = isAuthorized(
config.user,
'/permission/admin/app-mgt/publisher/admin/application/update',
);
axios axios
.get( .get(
window.location.origin + window.location.origin +
@ -84,18 +89,9 @@ class ManageCategories extends React.Component {
'Error occured while trying to load categories', 'Error occured while trying to load categories',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.categories = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
} }
@ -157,36 +153,40 @@ class ManageCategories extends React.Component {
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
> >
{categoryName} {categoryName}
<Divider type="vertical" /> {this.hasPermissionToManage && (
<Tooltip title="edit"> <>
<Icon <Divider type="vertical" />
onClick={() => { <Tooltip title="edit">
this.openEditModal(categoryName); <Icon
}} onClick={() => {
type="edit" this.openEditModal(categoryName);
/> }}
</Tooltip> type="edit"
<Divider type="vertical" /> />
<Tooltip title="delete"> </Tooltip>
<Popconfirm <Divider type="vertical" />
title="Are you sure delete this category?" <Tooltip title="delete">
onConfirm={() => { <Popconfirm
if (category.isCategoryDeletable) { title="Are you sure delete this category?"
this.deleteCategory(categoryName); onConfirm={() => {
} else { if (category.isCategoryDeletable) {
notification.error({ this.deleteCategory(categoryName);
message: 'Cannot delete "' + categoryName + '"', } else {
description: notification.error({
'This category is currently used. Please unassign the category from apps.', message: 'Cannot delete "' + categoryName + '"',
}); description:
} 'This category is currently used. Please unassign the category from apps.',
}} });
okText="Yes" }
cancelText="No" }}
> okText="Yes"
<Icon type="delete" /> cancelText="No"
</Popconfirm> >
</Tooltip> <Icon type="delete" />
</Popconfirm>
</Tooltip>
</>
)}
</Tag> </Tag>
); );
return ( return (
@ -377,18 +377,16 @@ class ManageCategories extends React.Component {
inputValue, inputValue,
tempElements, tempElements,
isAddNewVisible, isAddNewVisible,
forbiddenErrors,
} = this.state; } = this.state;
const categoriesElements = categories.map(this.renderElement); const categoriesElements = categories.map(this.renderElement);
const temporaryElements = tempElements.map(this.renderTempElement); const temporaryElements = tempElements.map(this.renderTempElement);
return ( return (
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
{forbiddenErrors.categories && ( {!this.hasPermissionToManage && (
<Alert <Alert
message="You don't have permission to view categories." message="You don't have permission to add / edit / delete categories."
type="warning" type="warning"
banner banner
closable
/> />
)} )}
<Card> <Card>
@ -414,6 +412,7 @@ class ManageCategories extends React.Component {
); );
}} }}
htmlType="button" htmlType="button"
disabled={!this.hasPermissionToManage}
> >
Add Add
</Button> </Button>

@ -39,6 +39,7 @@ import axios from 'axios';
import { TweenOneGroup } from 'rc-tween-one'; import { TweenOneGroup } from 'rc-tween-one';
import { withConfigContext } from '../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../services/utils/errorHandler';
import { isAuthorized } from '../../../../../../services/utils/authorizationHandler';
const { Title } = Typography; const { Title } = Typography;
@ -61,6 +62,10 @@ class ManageTags extends React.Component {
componentDidMount() { componentDidMount() {
const config = this.props.context; const config = this.props.context;
this.hasPermissionToManage = isAuthorized(
config.user,
'/permission/admin/app-mgt/publisher/admin/application/update',
);
axios axios
.get( .get(
window.location.origin + window.location.origin +
@ -83,18 +88,9 @@ class ManageTags extends React.Component {
'Error occurred while trying to load tags.', 'Error occurred while trying to load tags.',
true, true,
); );
if (error.hasOwnProperty('response') && error.response.status === 403) { this.setState({
const { forbiddenErrors } = this.state; loading: false,
forbiddenErrors.tags = true; });
this.setState({
forbiddenErrors,
loading: false,
});
} else {
this.setState({
loading: false,
});
}
}); });
} }
@ -151,36 +147,40 @@ class ManageTags extends React.Component {
const tagElem = ( const tagElem = (
<Tag color="#34495e" style={{ marginTop: 8 }}> <Tag color="#34495e" style={{ marginTop: 8 }}>
{tagName} {tagName}
<Divider type="vertical" /> {this.hasPermissionToManage && (
<Tooltip title="edit"> <>
<Icon <Divider type="vertical" />
onClick={() => { <Tooltip title="edit">
this.openEditModal(tagName); <Icon
}} onClick={() => {
type="edit" this.openEditModal(tagName);
/> }}
</Tooltip> type="edit"
<Divider type="vertical" /> />
<Tooltip title="delete"> </Tooltip>
<Popconfirm <Divider type="vertical" />
title="Are you sure delete this tag?" <Tooltip title="delete">
onConfirm={() => { <Popconfirm
if (tag.isTagDeletable) { title="Are you sure delete this tag?"
this.deleteTag(tagName); onConfirm={() => {
} else { if (tag.isTagDeletable) {
notification.error({ this.deleteTag(tagName);
message: 'Cannot delete "' + tagName + '"', } else {
description: notification.error({
'This tag is currently used. Please unassign the tag from apps.', message: 'Cannot delete "' + tagName + '"',
}); description:
} 'This tag is currently used. Please unassign the tag from apps.',
}} });
okText="Yes" }
cancelText="No" }}
> okText="Yes"
<Icon type="delete" /> cancelText="No"
</Popconfirm> >
</Tooltip> <Icon type="delete" />
</Popconfirm>
</Tooltip>
</>
)}
</Tag> </Tag>
); );
return ( return (
@ -368,18 +368,16 @@ class ManageTags extends React.Component {
inputValue, inputValue,
tempElements, tempElements,
isAddNewVisible, isAddNewVisible,
forbiddenErrors,
} = this.state; } = this.state;
const tagsElements = tags.map(this.renderElement); const tagsElements = tags.map(this.renderElement);
const temporaryElements = tempElements.map(this.renderTempElement); const temporaryElements = tempElements.map(this.renderTempElement);
return ( return (
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
{forbiddenErrors.tags && ( {!this.hasPermissionToManage && (
<Alert <Alert
message="You don't have permission to view tags." message="You don't have permission to add / edit / delete tags."
type="warning" type="warning"
banner banner
closable
/> />
)} )}
<Card> <Card>
@ -405,6 +403,7 @@ class ManageTags extends React.Component {
); );
}} }}
htmlType="button" htmlType="button"
disabled={!this.hasPermissionToManage}
> >
Add Add
</Button> </Button>

@ -21,6 +21,7 @@ import { PageHeader, Typography, Breadcrumb, Row, Col, Icon } from 'antd';
import ManageCategories from './components/Categories'; import ManageCategories from './components/Categories';
import ManageTags from './components/Tags'; import ManageTags from './components/Tags';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Authorized from '../../../../components/Authorized/Authorized';
const { Paragraph } = Typography; const { Paragraph } = Typography;
@ -54,12 +55,19 @@ class Manage extends React.Component {
</PageHeader> </PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 780 }}>
<Row gutter={16}> <Row gutter={16}>
<Col sm={24} md={12}> <Authorized
<ManageCategories /> permission="/permission/admin/app-mgt/publisher/application/update"
</Col> yes={
<Col sm={24} md={12}> <>
<ManageTags /> <Col sm={24} md={12}>
</Col> <ManageCategories />
</Col>
<Col sm={24} md={12}>
<ManageTags />
</Col>
</>
}
/>
</Row> </Row>
</div> </div>
</div> </div>

@ -34,6 +34,7 @@ import './styles.css';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import AddNewPage from './components/AddNewPage'; import AddNewPage from './components/AddNewPage';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import { isAuthorized } from '../../../../../../../../services/utils/authorizationHandler';
const { Text, Title } = Typography; const { Text, Title } = Typography;
@ -47,6 +48,10 @@ class Pages extends React.Component {
selectedRows: [], selectedRows: [],
homePageId: null, homePageId: null,
}; };
this.hasPermissionToManage = isAuthorized(
this.props.config.user,
'/device-mgt/enterprise/user/view',
);
} }
rowSelection = { rowSelection = {
@ -226,34 +231,38 @@ class Pages extends React.Component {
key: 'actions', key: 'actions',
render: (name, page) => ( render: (name, page) => (
<div> <div>
<span className="action"> {this.hasPermissionToManage && (
<Button <>
disabled={page.id === this.state.homePageId} <span className="action">
className="btn-warning" <Button
icon="home" disabled={page.id === this.state.homePageId}
type="link" className="btn-warning"
onClick={() => { icon="home"
this.updateHomePage(page.id); type="link"
}} onClick={() => {
> this.updateHomePage(page.id);
set as homepage }}
</Button> >
</span> set as homepage
<Divider type="vertical" /> </Button>
<Popconfirm </span>
title="Are you sure" <Divider type="vertical" />
okText="Yes" <Popconfirm
cancelText="No" title="Are you sure"
onConfirm={() => { okText="Yes"
this.deletePage(page.id); cancelText="No"
}} onConfirm={() => {
> this.deletePage(page.id);
<span className="action"> }}
<Text type="danger"> >
<Icon type="delete" /> delete <span className="action">
</Text> <Text type="danger">
</span> <Icon type="delete" /> delete
</Popconfirm> </Text>
</span>
</Popconfirm>
</>
)}
</div> </div>
), ),
}, },

@ -17,12 +17,13 @@
*/ */
import React from 'react'; import React from 'react';
import { PageHeader, Breadcrumb, Divider, Icon } from 'antd'; import { PageHeader, Breadcrumb, Divider, Icon, Result } from 'antd';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import SyncAndroidApps from './components/SyncAndroidApps'; import SyncAndroidApps from './components/SyncAndroidApps';
import { withConfigContext } from '../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../components/ConfigContext';
import GooglePlayIframe from './components/GooglePlayIframe'; import GooglePlayIframe from './components/GooglePlayIframe';
import Pages from './components/Pages'; import Pages from './components/Pages';
import Authorized from '../../../../../../components/Authorized/Authorized';
class ManageAndroidEnterprise extends React.Component { class ManageAndroidEnterprise extends React.Component {
routes; routes;
@ -52,10 +53,27 @@ class ManageAndroidEnterprise extends React.Component {
</div> </div>
</PageHeader> </PageHeader>
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}> <div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}>
<SyncAndroidApps /> <Authorized
<GooglePlayIframe /> permission="/permission/admin/device-mgt/enterprise/user/view"
<Divider /> yes={
<Pages /> <>
<SyncAndroidApps />
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/modify"
yes={<GooglePlayIframe />}
/>
<Divider />
<Pages />
</>
}
no={
<Result
status="403"
title="You don't have permission to view android enterprise configurations."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
</div> </div>
); );

@ -295,44 +295,50 @@ class Cluster extends React.Component {
} }
return ( return (
<div className="product"> <div className="product">
<div className="arrow"> {this.props.hasPermissionToManage && (
<button <div className="arrow">
disabled={index === 0} <button
className="btn" disabled={index === 0}
onClick={() => { className="btn"
this.swapProduct(index, index - 1); onClick={() => {
}} this.swapProduct(index, index - 1);
> }}
<Icon type="caret-left" theme="filled" /> >
</button> <Icon type="caret-left" theme="filled" />
</div> </button>
</div>
)}
<div className="product-icon"> <div className="product-icon">
<img src={imageSrc} /> <img src={imageSrc} />
<Tooltip title={packageId}> <Tooltip title={packageId}>
<div className="title">{packageId}</div> <div className="title">{packageId}</div>
</Tooltip> </Tooltip>
</div> </div>
<div className="arrow"> {this.props.hasPermissionToManage && (
<button <>
disabled={index === products.length - 1} <div className="arrow">
onClick={() => { <button
this.swapProduct(index, index + 1); disabled={index === products.length - 1}
}} onClick={() => {
className="btn btn-right" this.swapProduct(index, index + 1);
> }}
<Icon type="caret-right" theme="filled" /> className="btn btn-right"
</button> >
</div> <Icon type="caret-right" theme="filled" />
<div className="delete-btn"> </button>
<button </div>
className="btn" <div className="delete-btn">
onClick={() => { <button
this.removeProduct(index); className="btn"
}} onClick={() => {
> this.removeProduct(index);
<Icon type="close-circle" theme="filled" /> }}
</button> >
</div> <Icon type="close-circle" theme="filled" />
</button>
</div>
</>
)}
</div> </div>
); );
}; };
@ -347,7 +353,7 @@ class Cluster extends React.Component {
</Title> </Title>
</Col> </Col>
<Col span={8}> <Col span={8}>
{!isTemporary && ( {!isTemporary && this.props.hasPermissionToManage && (
<div style={{ float: 'right' }}> <div style={{ float: 'right' }}>
<Tooltip title="Move Up"> <Tooltip title="Move Up">
<Button <Button
@ -391,10 +397,12 @@ class Cluster extends React.Component {
</Col> </Col>
</Row> </Row>
<div className="products-row"> <div className="products-row">
<AddAppsToClusterModal {this.props.hasPermissionToManage && (
addSelectedProducts={this.addSelectedProducts} <AddAppsToClusterModal
unselectedProducts={unselectedProducts} addSelectedProducts={this.addSelectedProducts}
/> unselectedProducts={unselectedProducts}
/>
)}
{products.map((product, index) => { {products.map((product, index) => {
return ( return (
<Product <Product

@ -30,6 +30,7 @@ import {
Spin, Spin,
Tag, Tag,
Divider, Divider,
Result,
} from 'antd'; } from 'antd';
import { Link, withRouter } from 'react-router-dom'; import { Link, withRouter } from 'react-router-dom';
import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import { withConfigContext } from '../../../../../../../../components/ConfigContext';
@ -37,6 +38,8 @@ import axios from 'axios';
import Cluster from './components/Cluster'; import Cluster from './components/Cluster';
import EditLinks from './components/EditLinks'; import EditLinks from './components/EditLinks';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler'; import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../../../services/utils/authorizationHandler';
const { Title } = Typography; const { Title } = Typography;
@ -59,6 +62,10 @@ class Page extends React.Component {
isAddNewClusterVisible: false, isAddNewClusterVisible: false,
links: [], links: [],
}; };
this.hasPermissionToManage = isAuthorized(
this.props.config.user,
'/device-mgt/enterprise/user/view',
);
} }
componentDidMount() { componentDidMount() {
@ -328,94 +335,121 @@ class Page extends React.Component {
{/* <Paragraph>Lorem ipsum</Paragraph>*/} {/* <Paragraph>Lorem ipsum</Paragraph>*/}
</div> </div>
</PageHeader> </PageHeader>
<Spin spinning={loading}> <Authorized
<div style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}> permission="/permission/admin/device-mgt/enterprise/user/view"
<Row> yes={
<Col md={8} sm={18} xs={24}> <Spin spinning={loading}>
<Title editable={{ onChange: this.updatePageName }} level={2}> <div
{pageName} style={{ background: '#f0f2f5', padding: 24, minHeight: 720 }}
</Title>
</Col>
</Row>
<Row>
<Col>
<Title level={4}>Links</Title>
{links.map(link => {
if (this.pageNames.hasOwnProperty(link.toString())) {
return (
<Tag key={link} color="#87d068">
{this.pageNames[link.toString()]}
</Tag>
);
}
return null;
})}
<EditLinks
updateLinks={this.updateLinks}
pageId={this.pageId}
selectedLinks={links}
pages={this.pages}
/>
</Col>
{/* <Col>*/}
{/* </Col>*/}
</Row>
<Divider dashed={true} />
<Title level={4}>Clusters</Title>
<div
hidden={isAddNewClusterVisible}
style={{ textAlign: 'center' }}
>
<Button
type="dashed"
shape="round"
icon="plus"
size="large"
onClick={() => {
this.toggleAddNewClusterVisibility(true);
}}
> >
Add new cluster <Row>
</Button> <Col md={8} sm={18} xs={24}>
</div> <Title
<div hidden={!isAddNewClusterVisible}> editable={{ onChange: this.updatePageName }}
<Cluster level={2}
cluster={{ >
clusterId: 0, {pageName}
name: 'New Cluster', </Title>
products: [], </Col>
}} </Row>
orderInPage={clusters.length} <Row>
isTemporary={true} <Col>
pageId={this.pageId} <Title level={4}>Links</Title>
applications={applications} {links.map(link => {
addSavedClusterToThePage={this.addSavedClusterToThePage} if (this.pageNames.hasOwnProperty(link.toString())) {
toggleAddNewClusterVisibility={ return (
this.toggleAddNewClusterVisibility <Tag key={link} color="#87d068">
} {this.pageNames[link.toString()]}
/> </Tag>
</div> );
}
{clusters.map((cluster, index) => { return null;
return ( })}
<Cluster <Authorized
key={cluster.clusterId} permission="/permission/admin/device-mgt/enterprise/user/modify"
index={index} yes={
orderInPage={cluster.orderInPage} <EditLinks
isTemporary={false} updateLinks={this.updateLinks}
cluster={cluster} pageId={this.pageId}
pageId={this.pageId} selectedLinks={links}
applications={applications} pages={this.pages}
swapClusters={this.swapClusters} />
removeLoadedCluster={this.removeLoadedCluster} }
/>
</Col>
{/* <Col>*/}
{/* </Col>*/}
</Row>
<Divider dashed={true} />
<Title level={4}>Clusters</Title>
<Authorized
permission="/permission/admin/device-mgt/enterprise/user/modify"
yes={
<div
hidden={isAddNewClusterVisible}
style={{ textAlign: 'center' }}
>
<Button
type="dashed"
shape="round"
icon="plus"
size="large"
onClick={() => {
this.toggleAddNewClusterVisibility(true);
}}
>
Add new cluster
</Button>
</div>
}
/> />
); <div hidden={!isAddNewClusterVisible}>
})} <Cluster
</div> cluster={{
</Spin> clusterId: 0,
name: 'New Cluster',
products: [],
}}
orderInPage={clusters.length}
isTemporary={true}
pageId={this.pageId}
applications={applications}
addSavedClusterToThePage={this.addSavedClusterToThePage}
toggleAddNewClusterVisibility={
this.toggleAddNewClusterVisibility
}
/>
</div>
{clusters.map((cluster, index) => {
return (
<Cluster
hasPermissionToManage={this.hasPermissionToManage}
key={cluster.clusterId}
index={index}
orderInPage={cluster.orderInPage}
isTemporary={false}
cluster={cluster}
pageId={this.pageId}
applications={applications}
swapClusters={this.swapClusters}
removeLoadedCluster={this.removeLoadedCluster}
/>
);
})}
</div>
</Spin>
}
no={
<Result
status="403"
title="You don't have permission to view android enterprise configurations."
subTitle="Please contact system administrator"
/>
}
/>
</div> </div>
); );
} }

@ -0,0 +1,24 @@
/*
* 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.
*/
export const isAuthorized = (user, permission) => {
if (!user || !permission) {
return false;
}
return user.permissions.includes(permission);
};
Loading…
Cancel
Save