Conflicts:
	components/policy-mgt/org.wso2.carbon.policy.mgt.core/src/main/java/org/wso2/carbon/policy/mgt/core/mgt/impl/PolicyManagerImpl.java
revert-70ac1926
Charitha Goonetilleke 4 years ago
commit 5d6c614a7a

@ -232,4 +232,4 @@ public class AuthenticationHandler extends AbstractHandler {
map.put(CONTENT_TYPE, "application/json");
return map;
}
}
}

@ -24,6 +24,7 @@ public class AuthConstants {
public static final String MDM_SIGNATURE = "mdm-signature";
public static final String PROXY_MUTUAL_AUTH_HEADER = "proxy-mutual-auth-header";
public static final String MUTUAL_AUTH_HEADER = "mutual-auth-header";
public static final String ONE_TIME_TOKEN_HEADER = "one-time-token";
public static final String ENCODED_PEM = "encoded-pem";
public static final String CALLBACK_URL = "";
public static final String CLIENT_NAME = "IOT-API-MANAGER";

@ -21,6 +21,7 @@ import org.wso2.carbon.device.application.mgt.common.ExecutionStatus;
import org.wso2.carbon.device.application.mgt.common.dto.ScheduledSubscriptionDTO;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
import org.wso2.carbon.device.application.mgt.common.exception.SubscriptionManagementException;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import java.util.List;
@ -101,6 +102,18 @@ public interface SubscriptionManager {
<T> void performEntAppSubscription(String applicationUUID, List<T> params, String subType, String action,
boolean requiresUpdatingExternal) throws ApplicationManagementException;
/**
* Install given application releases for given device. If application is already installed that application skips.
* This is used in enterprise app installing policy.
*
* @param deviceIdentifier Device identifiers
* @param releaseUUID UUIs of applicatios
* @throws ApplicationManagementException if error occurred while installing given applications into the given
* device
*/
void installAppsForDevice(DeviceIdentifier deviceIdentifier, List<String> releaseUUID)
throws ApplicationManagementException;
/***
* This method used to get the app id ,device ids and pass them to DM service method.
*

@ -2029,8 +2029,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
appUnrestrictedRoles = applicationUpdateWrapper.getUnrestrictedRoles();
} else {
List<String> addingRoleList = getDifference(applicationUpdateWrapper.getUnrestrictedRoles(),
applicationDTO.getUnrestrictedRoles());
List<String> removingRoleList = getDifference(applicationDTO.getUnrestrictedRoles(),
appUnrestrictedRoles);
List<String> removingRoleList = getDifference(appUnrestrictedRoles,
applicationUpdateWrapper.getUnrestrictedRoles());
if (!addingRoleList.isEmpty()) {
visibilityDAO.addUnrestrictedRoles(addingRoleList, applicationId, tenantId);

@ -94,6 +94,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
@ -261,7 +262,7 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
log.error(msg, e);
throw new SubscriptionManagementException(msg, e);
} catch (DBConnectionException e) {
String msg = "Error occurred while retrieving the database connection";
String msg = "Error occurred while retrieving the database connection to clean the scheduled subscriptions";
log.error(msg, e);
throw new SubscriptionManagementException(msg, e);
} finally {
@ -454,6 +455,70 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
}
}
@Override public void installAppsForDevice(DeviceIdentifier deviceIdentifier, List<String> releaseUUIDs)
throws ApplicationManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
Device device;
try {
device = DataHolder.getInstance().getDeviceManagementService().getDevice(deviceIdentifier, false);
if (device == null) {
String msg = "Invalid device identifier is received and couldn't find an deveice for the requested "
+ "device identifier. Device UUID: " + deviceIdentifier.getId() + " Device Type: "
+ deviceIdentifier.getType();
log.error(msg);
throw new BadRequestException(msg);
}
} catch (DeviceManagementException e) {
String msg = "Error occured while getting device data for given device identifier.Device UUID: "
+ deviceIdentifier.getId() + " Device Type: " + deviceIdentifier.getType();
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
}
List<DeviceIdentifier> appInstallingDevices = new ArrayList<>();
for (String releaseUUID : releaseUUIDs) {
try {
ConnectionManagerUtil.openDBConnection();
ApplicationDTO applicationDTO = this.applicationDAO.getAppWithRelatedRelease(releaseUUID, tenantId);
if (applicationDTO != null) {
List<DeviceSubscriptionDTO> deviceSubscriptionDTOS = this.subscriptionDAO
.getDeviceSubscriptions(applicationDTO.getApplicationReleaseDTOs().get(0).getId(),
tenantId);
AtomicBoolean isAppSubscribable = new AtomicBoolean(true);
for (DeviceSubscriptionDTO deviceSubscriptionDTO : deviceSubscriptionDTOS) {
if (device.getId() == deviceSubscriptionDTO.getDeviceId() && !deviceSubscriptionDTO
.isUnsubscribed()) {
isAppSubscribable.set(false);
break;
}
}
if (isAppSubscribable.get()) {
appInstallingDevices.add(deviceIdentifier);
}
}
} catch (DBConnectionException e) {
String msg = " Error occurred while getting DB connection to retrieve app data data from DB. Device "
+ "UUID: " + deviceIdentifier.getId() + " Device Type: " + deviceIdentifier.getType();
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} catch (ApplicationManagementDAOException e) {
String msg = " Error occurred while getting application data from DB. Device UUID: " + deviceIdentifier
.getId() + " Device Type: " + deviceIdentifier.getType();
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} finally {
ConnectionManagerUtil.closeDBConnection();
}
if (!appInstallingDevices.isEmpty()) {
performBulkAppOperation(releaseUUID, appInstallingDevices, SubscriptionType.DEVICE.toString(),
SubAction.INSTALL.toString());
}
}
}
/**
* This method is responsible to update subscription data for google enterprise install.
*
@ -584,13 +649,11 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
List<DeviceIdentifier> identifiers;
if (!deviceIdentifierMap.containsKey(identifier.getType())) {
identifiers = new ArrayList<>();
identifiers.add(identifier);
deviceIdentifierMap.put(identifier.getType(), identifiers);
} else {
identifiers = deviceIdentifierMap.get(identifier.getType());
identifiers.add(identifier);
deviceIdentifierMap.put(identifier.getType(), identifiers);
}
identifiers.add(identifier);
deviceIdentifierMap.put(identifier.getType(), identifiers);
}
for (Map.Entry<String, List<DeviceIdentifier>> entry : deviceIdentifierMap.entrySet()) {
Activity activity = addAppOperationOnDevices(applicationDTO, new ArrayList<>(entry.getValue()),

@ -66,7 +66,6 @@
<phase>generate-resources</phase>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<npmVersion>${npm.version}</npmVersion>
</configuration>
</execution>
<execution>
@ -101,46 +100,6 @@
</execution>
</executions>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.codehaus.mojo</groupId>-->
<!-- <artifactId>exec-maven-plugin</artifactId>-->
<!-- <version>1.5.0</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>npm install (initialize)</id>-->
<!-- <goals>-->
<!-- <goal>exec</goal>-->
<!-- </goals>-->
<!-- <phase>initialize</phase>-->
<!-- <configuration>-->
<!-- <workingDirectory>react-app</workingDirectory>-->
<!-- <executable>${npm.executable}</executable>-->
<!-- <arguments>-->
<!-- <argument>install</argument>-->
<!-- <argument>&#45;&#45;silent</argument>-->
<!-- </arguments>-->
<!-- </configuration>-->
<!-- </execution>-->
<!-- <execution>-->
<!-- <id>npm run build (compile)</id>-->
<!-- <goals>-->
<!-- <goal>exec</goal>-->
<!-- </goals>-->
<!-- <phase>compile</phase>-->
<!-- <configuration>-->
<!-- <workingDirectory>react-app</workingDirectory>-->
<!-- <executable>${npm.executable}</executable>-->
<!-- <arguments>-->
<!-- <argument>run</argument>-->
<!-- <argument>${npm.build.command}</argument>-->
<!-- </arguments>-->
<!-- </configuration>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- <configuration>-->
<!-- <workingDirectory>${npm.working.dir}</workingDirectory>-->
<!-- </configuration>-->
<!-- </plugin>-->
</plugins>
</build>
<profiles>

@ -10,15 +10,17 @@
},
"license": "Apache License 2.0",
"dependencies": {
"@ant-design/compatible": "^1.0.0",
"@ant-design/dark-theme": "^0.2.2",
"@ant-design/icons": "^4.0.6",
"@babel/polyfill": "^7.6.0",
"acorn": "^6.2.0",
"antd": "^3.23.6",
"antd": "^4.0.0",
"axios": "^0.19.0",
"babel-eslint": "^9.0.0",
"d3": "^5.9.7",
"dagre": "^0.8.4",
"entgra-icons-react": "^1.0.0",
"eslint": "^5.16.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
@ -58,6 +60,7 @@
"chai": "^4.1.2",
"cross-env": "^7.0.0",
"css-loader": "^0.28.11",
"entgra-icons": "^1.4.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "4.3.0",
"eslint-plugin-babel": "5.3.0",

@ -18,73 +18,69 @@
},
"defaultPlatformIcons": {
"default": {
"icon": "global",
"color": "#535c68",
"theme": "outlined"
"icon": "fw-globe",
"color": "#535c68"
},
"android": {
"icon": "android",
"color": "#7db343",
"theme": "filled"
"icon": "fw-android",
"color": "#7db343"
},
"ios": {
"icon": "apple",
"color": "#535c68",
"theme": "filled"
"icon": "fw-apple",
"color": "#535c68"
},
"windows": {
"icon": "windows",
"color": "#008cc4",
"theme": "filled"
"icon": "fw-windows",
"color": "#008cc4"
}
},
"lifecycle": {
"CREATED": {
"title": "Created",
"text": "The initial most state of an application.\n You can only proceed to one of the following states:",
"icon": "file-text",
"icon": "fw-document",
"step": 0
},
"IN-REVIEW": {
"title": "In-Review",
"text": "In this state the application is being reviewed by approvers. You can move from this state to APPROVED state if it is approved by reviewers. Otherwise, you can move the application into either REJECTED state or CREATED state based on the feedback getting by reviewers.",
"icon": "audit",
"icon": "fw-throttling-policy",
"step": 1
},
"APPROVED": {
"title": "Approved",
"text": "The approved state is a compulsory state prior to publishing the application.\n You can only proceed to one of the following states:",
"icon": "file-done",
"icon": "fw-check",
"step": 2
},
"PUBLISHED": {
"title": "Published",
"text": "The state which is applied for applications which are qualified for your Corporate App Store. Only the applications of Published state can be installed to your corporate devices.\n You can only proceed to one of the following states:",
"icon": "global",
"icon": "fw-globe",
"step": 3
},
"BLOCKED": {
"title": "Blocked",
"text": "This state allows you to block your application either to publish or deprecate at a future date.\n You can only proceed to one of the following states:",
"icon": "exception",
"icon": "fw-lock",
"step": 4
},
"DEPRECATED": {
"title": "Deprecated",
"text": "The applications which are outdated and no longer suit your app store.\n You can only proceed to one of the following states:",
"icon": "file-excel",
"icon": "fw-warning",
"step": 5
},
"REJECTED": {
"title": "Rejected",
"text": "The Approvers can reject an application due to a faulty of the app or not being in compliance with company policies.\n You can only proceed to one of the following states:",
"icon": "frown",
"icon": "fw-error",
"step": 6
},
"RETIRED": {
"title": "Retired",
"text": "The final state of an application, where no transition of states will be allowed after this.",
"icon": "rest",
"icon": "fw-delete",
"step": 7
}
},

@ -17,7 +17,8 @@
*/
import React from 'react';
import { notification, Menu, Icon } from 'antd';
import { LogoutOutlined } from '@ant-design/icons';
import { notification, Menu } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../components/ConfigContext';
@ -70,7 +71,7 @@ class Logout extends React.Component {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}>
<Icon type="logout" />
<LogoutOutlined />
Logout
</Menu.Item>
</Menu>

@ -17,7 +17,19 @@
*/
import React from 'react';
import { Layout, Menu, Icon, Drawer, Button } from 'antd';
import {
AndroidFilled,
AppstoreOutlined,
ControlOutlined,
PlusOutlined,
SettingOutlined,
UserOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons';
import { Layout, Menu, Drawer, Button } from 'antd';
import { Switch, Link } from 'react-router-dom';
import RouteWithSubRoutes from '../../components/RouteWithSubRoutes';
import { Redirect } from 'react-router';
@ -81,7 +93,7 @@ class Dashboard extends React.Component {
>
<Menu.Item key="1">
<Link to="/publisher/apps">
<Icon type="appstore" />
<AppstoreOutlined />
Apps
</Link>
</Menu.Item>
@ -92,7 +104,7 @@ class Dashboard extends React.Component {
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="plus" />
<PlusOutlined />
Add New App
</span>
}
@ -118,21 +130,20 @@ class Dashboard extends React.Component {
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="control" />
<ControlOutlined />
Manage
</span>
}
>
<Menu.Item key="manage">
<Link to="/publisher/manage">
<Icon type="setting" /> General
<SettingOutlined /> General
</Link>
</Menu.Item>
{this.config.androidEnterpriseToken != null && (
<Menu.Item key="manage-android-enterprise">
<Link to="/publisher/manage/android-enterprise">
<Icon type="android" theme="filled" /> Android
Enterprise
<AndroidFilled /> Android Enterprise
</Link>
</Menu.Item>
)}
@ -142,7 +153,7 @@ class Dashboard extends React.Component {
className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
<UserOutlined />
{this.config.username}
</span>
}
@ -157,10 +168,11 @@ class Dashboard extends React.Component {
<Layout className="mobile-layout">
<div className="mobile-menu-button">
<Button type="link" onClick={this.showMobileNavigationBar}>
<Icon
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
className="bar-icon"
/>
{this.state.collapsed ? (
<MenuFoldOutlined />
) : (
<MenuUnfoldOutlined />
)}
</Button>
</div>
</Layout>
@ -194,14 +206,14 @@ class Dashboard extends React.Component {
>
<Menu.Item key="1">
<Link to="/publisher/apps">
<Icon type="appstore" />
<AppstoreOutlined />
Apps
</Link>
</Menu.Item>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="plus" />
<PlusOutlined />
Add New App
</span>
}
@ -223,7 +235,7 @@ class Dashboard extends React.Component {
</SubMenu>
<Menu.Item key="2">
<Link to="/publisher/manage">
<Icon type="control" />
<ControlOutlined />
Manage
</Link>
</Menu.Item>
@ -238,7 +250,7 @@ class Dashboard extends React.Component {
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
<UserOutlined />
</span>
}
>

@ -17,7 +17,9 @@
*/
import React from 'react';
import { Alert, Button, Col, Form, Input, Row, Select, Spin } from 'antd';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Alert, Button, Col, Input, Row, Select, Spin } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';

@ -17,11 +17,12 @@
*/
import React from 'react';
import { MinusOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Button,
Col,
Form,
Icon,
Input,
Row,
Select,
@ -280,7 +281,7 @@ class NewAppUploadForm extends React.Component {
} = this.state;
const uploadButton = (
<div>
<Icon type="plus" />
<PlusOutlined />
<div className="ant-upload-text">Select</div>
</div>
);
@ -315,7 +316,7 @@ class NewAppUploadForm extends React.Component {
>
{binaryFiles.length !== 1 && (
<Button>
<Icon type="upload" /> Click to upload
<UploadOutlined /> Click to upload
</Button>
)}
</Upload>,
@ -577,7 +578,7 @@ class NewAppUploadForm extends React.Component {
<Button
type="dashed"
shape="circle"
icon="minus"
icon={<MinusOutlined />}
onClick={() => {
metaData.splice(index, 1);
this.setState({
@ -592,7 +593,7 @@ class NewAppUploadForm extends React.Component {
})}
<Button
type="dashed"
icon="plus"
icon={<PlusOutlined />}
onClick={this.addNewMetaData}
>
Add

@ -17,7 +17,9 @@
*/
import React from 'react';
import { Card, Button, Steps, Row, Col, Form, Result, Spin } from 'antd';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Card, Button, Steps, Row, Col, Result, Spin } from 'antd';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
import NewAppDetailsForm from './components/NewAppDetailsForm';

@ -17,7 +17,8 @@
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon, Result } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
@ -58,7 +59,7 @@ class AddNewCustomApp extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Custom App</Breadcrumb.Item>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Icon, Result } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
@ -52,7 +53,7 @@ class AddNewEnterpriseApp extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Enterprise App</Breadcrumb.Item>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Icon, PageHeader, Typography, Breadcrumb, Result } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
@ -59,7 +60,7 @@ class AddNewPublicApp extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Public App</Breadcrumb.Item>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Icon, PageHeader, Typography, Breadcrumb, Result } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Result } from 'antd';
import AddNewAppForm from '../../components/AddNewAppForm';
import { Link } from 'react-router-dom';
import Authorized from '../../../../../../components/Authorized/Authorized';
@ -55,7 +56,7 @@ class AddNewEnterpriseApp extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Web Clip</Breadcrumb.Item>

@ -17,7 +17,9 @@
*/
import React from 'react';
import { Form, notification, Spin, Card, Row, Col } from 'antd';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { notification, Spin, Card, Row, Col } from 'antd';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
import { withConfigContext } from '../../../../../../components/ConfigContext';

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Icon, PageHeader, Typography, Breadcrumb } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb } from 'antd';
import AddNewReleaseForm from './components/AddNewReleaseForm';
import { Link } from 'react-router-dom';
@ -40,7 +41,7 @@ class AddNewRelease extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Add New Release</Breadcrumb.Item>

@ -18,6 +18,7 @@
import React from 'react';
import {
Alert,
Drawer,
Select,
Avatar,
@ -29,7 +30,6 @@ import {
Button,
Spin,
message,
Icon,
Card,
Badge,
Tooltip,
@ -48,7 +48,14 @@ import ManagedConfigurationsIframe from './components/ManagedConfigurationsIfram
import { handleApiError } from '../../../../../../../../../services/utils/errorHandler';
import Authorized from '../../../../../../../../../components/Authorized/Authorized';
import { isAuthorized } from '../../../../../../../../../services/utils/authorizationHandler';
import { MoreOutlined } from '@ant-design/icons';
import {
CheckCircleOutlined,
EditOutlined,
MoreOutlined,
StarOutlined,
UploadOutlined,
CheckOutlined,
} from '@ant-design/icons';
import DeleteApp from './components/DeleteApp';
import RetireApp from './components/RetireApp';
@ -56,13 +63,6 @@ const { Meta } = Card;
const { Text, Title } = Typography;
const { Option } = Select;
const IconText = ({ type, text }) => (
<span>
<Icon type={type} style={{ marginRight: 8 }} />
{text}
</span>
);
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike', 'blockquote'],
@ -85,6 +85,7 @@ const formats = [
class AppDetailsDrawer extends React.Component {
constructor(props) {
super(props);
this.config = this.props.context;
const drawerWidth = window.innerWidth <= 770 ? '80%' : '40%';
this.state = {
@ -93,14 +94,18 @@ class AppDetailsDrawer extends React.Component {
description: null,
globalCategories: [],
globalTags: [],
globalUnrestrictedRoles: [],
categories: [],
tags: [],
unrestrictedRoles: [],
temporaryDescription: null,
temporaryCategories: [],
temporaryTags: [],
temporaryUnrestrictedRoles: [],
isDescriptionEditEnabled: false,
isCategoriesEditEnabled: false,
isTagsEditEnabled: false,
isUnrestrictedRolesEditEnabled: false,
drawer: null,
drawerWidth,
};
@ -115,31 +120,39 @@ class AppDetailsDrawer extends React.Component {
) {
this.getCategories();
this.getTags();
this.getUnrestrictedRoles();
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.app !== this.props.app) {
const { name, description, tags, categories } = this.props.app;
const {
name,
description,
tags,
categories,
unrestrictedRoles,
} = this.props.app;
this.setState({
name,
description,
tags,
categories,
unrestrictedRoles,
isDescriptionEditEnabled: false,
isCategoriesEditEnabled: false,
isTagsEditEnabled: false,
isUnrestrictedRolesEditEnabled: false,
});
}
}
getCategories = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/categories',
)
.then(res => {
@ -172,12 +185,11 @@ class AppDetailsDrawer extends React.Component {
};
getTags = () => {
const config = this.props.context;
axios
.get(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/tags',
)
.then(res => {
@ -202,17 +214,46 @@ class AppDetailsDrawer extends React.Component {
});
};
getUnrestrictedRoles = () => {
axios
.get(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.deviceMgt +
'/roles',
)
.then(res => {
if (res.status === 200) {
const globalUnrestrictedRoles = res.data.data.roles;
this.setState({
globalUnrestrictedRoles,
loading: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to load roles.',
true,
);
this.setState({
loading: false,
});
});
};
// change the app name
handleNameSave = name => {
const config = this.props.context;
const { id } = this.props.app;
if (name !== this.state.name && name !== '') {
const data = { name: name };
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
@ -289,7 +330,6 @@ class AppDetailsDrawer extends React.Component {
// change app categories
handleCategorySave = () => {
const config = this.props.context;
const { id } = this.props.app;
const { temporaryCategories, categories } = this.state;
@ -302,8 +342,8 @@ class AppDetailsDrawer extends React.Component {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
@ -364,7 +404,6 @@ class AppDetailsDrawer extends React.Component {
// change app tags
handleTagsSave = () => {
const config = this.props.context;
const { id } = this.props.app;
const { temporaryTags, tags } = this.state;
@ -377,8 +416,8 @@ class AppDetailsDrawer extends React.Component {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
@ -386,6 +425,7 @@ class AppDetailsDrawer extends React.Component {
.then(res => {
if (res.status === 200) {
const app = res.data.data;
this.props.onUpdateApp('tags', temporaryTags);
notification.success({
message: 'Saved!',
description: 'App tags updated successfully!',
@ -417,9 +457,75 @@ class AppDetailsDrawer extends React.Component {
}
};
enableUnrestrictedRolesEdit = () => {
this.setState({
isUnrestrictedRolesEditEnabled: true,
temporaryUnrestrictedRoles: this.state.unrestrictedRoles,
});
};
disableUnrestrictedRolesEdit = () => {
this.setState({
isUnrestrictedRolesEditEnabled: false,
});
};
handleUnrestrictedRolesChange = temporaryUnrestrictedRoles => {
this.setState({ temporaryUnrestrictedRoles });
};
handleUnrestrictedRolesSave = () => {
const { id } = this.props.app;
const { temporaryUnrestrictedRoles, unrestrictedRoles } = this.state;
temporaryUnrestrictedRoles
.filter(x => !unrestrictedRoles.includes(x))
.concat(
unrestrictedRoles.filter(x => !temporaryUnrestrictedRoles.includes(x)),
);
const data = { unrestrictedRoles: temporaryUnrestrictedRoles };
axios
.put(
window.location.origin +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
)
.then(res => {
if (res.status === 200) {
const app = res.data.data;
this.props.onUpdateApp(
'unrestrictedRoles',
temporaryUnrestrictedRoles,
);
notification.success({
message: 'Saved!',
description: 'App unrestricted roles updated successfully!',
});
this.setState({
loading: false,
unrestrictedRoles: app.unrestrictedRoles,
isUnrestrictedRolesEditEnabled: false,
});
}
})
.catch(error => {
handleApiError(
error,
'Error occurred while trying to update unrestricted roles.',
true,
);
this.setState({
loading: false,
});
});
};
// handle description save
handleDescriptionSave = () => {
const config = this.props.context;
const { id } = this.props.app;
const { description, temporaryDescription } = this.state;
@ -431,8 +537,8 @@ class AppDetailsDrawer extends React.Component {
axios
.put(
window.location.origin +
config.serverConfig.invoker.uri +
config.serverConfig.invoker.publisher +
this.config.serverConfig.invoker.uri +
this.config.serverConfig.invoker.publisher +
'/applications/' +
id,
data,
@ -470,7 +576,6 @@ class AppDetailsDrawer extends React.Component {
};
render() {
const config = this.props.context;
const { app, visible, onClose } = this.props;
const {
name,
@ -479,13 +584,17 @@ class AppDetailsDrawer extends React.Component {
isDescriptionEditEnabled,
isCategoriesEditEnabled,
isTagsEditEnabled,
isUnrestrictedRolesEditEnabled,
temporaryDescription,
temporaryCategories,
temporaryTags,
temporaryUnrestrictedRoles,
globalCategories,
globalTags,
globalUnrestrictedRoles,
categories,
tags,
unrestrictedRoles,
} = this.state;
if (app == null) {
return null;
@ -503,7 +612,7 @@ class AppDetailsDrawer extends React.Component {
style={{
marginBottom: 10,
borderRadius: '28%',
backgroundColor: pSBC(0.5, config.theme.primaryColor),
backgroundColor: pSBC(0.5, this.config.theme.primaryColor),
}}
>
{avatarLetter}
@ -544,9 +653,9 @@ class AppDetailsDrawer extends React.Component {
<Menu.Item key="1">
<RetireApp id={id} isHideableApp={app.isHideableApp} />
</Menu.Item>
{config.androidEnterpriseToken !== null &&
{this.config.androidEnterpriseToken !== null &&
isAuthorized(
config.user,
this.config.user,
'/permission/admin/device-mgt/enterprise/user/modify',
) && (
<Menu.Item key="2">
@ -603,13 +712,12 @@ class AppDetailsDrawer extends React.Component {
title="Published"
count={
<Tooltip title="Published">
<Icon
<CheckCircleOutlined
style={{
backgroundColor: '#52c41a',
borderRadius: '50%',
color: 'white',
}}
type="check-circle"
/>
</Tooltip>
}
@ -633,24 +741,15 @@ class AppDetailsDrawer extends React.Component {
description={
<div
style={{
fontSize: '0.7em',
fontSize: '0.8em',
}}
className="description-view"
>
<IconText
type="check"
text={release.currentStatus}
/>
<CheckOutlined /> {release.currentStatus}
<Divider type="vertical" />
<IconText
type="upload"
text={release.releaseType}
/>
<UploadOutlined /> {release.releaseType}
<Divider type="vertical" />
<IconText
type="star-o"
text={release.rating.toFixed(1)}
/>
<StarOutlined /> {release.rating.toFixed(1)}
</div>
}
/>
@ -693,12 +792,12 @@ class AppDetailsDrawer extends React.Component {
!isDescriptionEditEnabled && (
<Text
style={{
color: config.theme.primaryColor,
color: this.config.theme.primaryColor,
cursor: 'pointer',
}}
onClick={this.enableDescriptionEdit}
>
<Icon type="edit" />
<EditOutlined />
</Text>
)
}
@ -749,12 +848,12 @@ class AppDetailsDrawer extends React.Component {
!isCategoriesEditEnabled && (
<Text
style={{
color: config.theme.primaryColor,
color: this.config.theme.primaryColor,
cursor: 'pointer',
}}
onClick={this.enableCategoriesEdit}
>
<Icon type="edit" />
<EditOutlined />
</Text>
)
}
@ -797,7 +896,7 @@ class AppDetailsDrawer extends React.Component {
{categories.map(category => {
return (
<Tag
color={pSBC(0.3, config.theme.primaryColor)}
color={pSBC(0.3, this.config.theme.primaryColor)}
key={category}
style={{ marginBottom: 5 }}
>
@ -816,12 +915,12 @@ class AppDetailsDrawer extends React.Component {
!isTagsEditEnabled && (
<Text
style={{
color: config.theme.primaryColor,
color: this.config.theme.primaryColor,
cursor: 'pointer',
}}
onClick={this.enableTagsEdit}
>
<Icon type="edit" />
<EditOutlined />
</Text>
)
}
@ -870,6 +969,85 @@ class AppDetailsDrawer extends React.Component {
})}
</span>
)}
<Divider dashed={true} />
<Text strong={true}>Unrestricted Roles</Text>
<Authorized
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
!isUnrestrictedRolesEditEnabled && (
<Text
style={{
color: this.config.theme.primaryColor,
cursor: 'pointer',
}}
onClick={this.enableUnrestrictedRolesEdit}
>
<EditOutlined />
</Text>
)
}
/>
<br />
<br />
{!unrestrictedRoles.length && (
<Alert
message="Application is not restricted to any roles."
type="info"
showIcon
/>
)}
{isUnrestrictedRolesEditEnabled && (
<div>
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Please select unrestricted roles"
onChange={this.handleUnrestrictedRolesChange}
value={temporaryUnrestrictedRoles}
>
{globalUnrestrictedRoles.map(unrestrictedRole => {
return (
<Option key={unrestrictedRole}>{unrestrictedRole}</Option>
);
})}
</Select>
<div style={{ marginTop: 10 }}>
<Button
style={{ marginRight: 10 }}
size="small"
htmlType="button"
onClick={this.disableUnrestrictedRolesEdit}
>
Cancel
</Button>
<Button
size="small"
type="primary"
htmlType="button"
onClick={this.handleUnrestrictedRolesSave}
>
Save
</Button>
</div>
</div>
)}
{!isUnrestrictedRolesEditEnabled && (
<span>
{unrestrictedRoles.map(unrestrictedRole => {
return (
<Tag
color={this.config.theme.primaryColor}
key={unrestrictedRole}
style={{ marginBottom: 5 }}
>
{unrestrictedRole}
</Tag>
);
})}
</span>
)}
<Authorized
permission="/permission/admin/app-mgt/publisher/review/view"
yes={

@ -17,13 +17,15 @@
*/
import React from 'react';
import { Avatar, Table, Tag, Icon, Badge, Alert, Tooltip } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import { Avatar, Table, Tag, Badge, Alert, Tooltip } from 'antd';
import axios from 'axios';
import pSBC from 'shade-blend-color';
import './styles.css';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import AppDetailsDrawer from './AppDetailsDrawer';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import { EntgraIcon } from 'entgra-icons-react';
let config = null;
@ -69,13 +71,12 @@ const columns = [
}}
count={
<Tooltip title="Published">
<Icon
<CheckCircleOutlined
style={{
backgroundColor: '#52c41a',
borderRadius: '50%',
color: 'white',
}}
type="check-circle"
/>
</Tooltip>
}
@ -139,15 +140,13 @@ const columns = [
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
color = defaultPlatformIcons[platform].color;
theme = defaultPlatformIcons[platform].theme;
}
return (
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
<EntgraIcon type={icon} />
</span>
);
},

@ -17,16 +17,9 @@
*/
import React from 'react';
import {
Card,
Col,
Row,
Typography,
Divider,
Select,
Button,
Form,
} from 'antd';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Card, Col, Row, Typography, Divider, Select, Button } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
@ -196,7 +189,8 @@ class FiltersForm extends React.Component {
permission="/permission/admin/app-mgt/publisher/application/update"
yes={
<div>
<Form.Item label="Categories">
<p>Categories:</p>
<Form.Item>
{getFieldDecorator('categories', {
rules: [
{
@ -221,7 +215,8 @@ class FiltersForm extends React.Component {
</Select>,
)}
</Form.Item>
<Form.Item label="Tags">
<p>Tags:</p>
<Form.Item>
{getFieldDecorator('tags', {
rules: [
{
@ -244,10 +239,11 @@ class FiltersForm extends React.Component {
</div>
}
/>
<p>Device Type:</p>
<Authorized
permission="/permission/admin/device-mgt/admin/device-type/view"
yes={
<Form.Item label="Device Type">
<Form.Item>
{getFieldDecorator('deviceType', {
rules: [
{
@ -271,7 +267,8 @@ class FiltersForm extends React.Component {
</Form.Item>
}
/>
<Form.Item label="App Type">
<p>App Type:</p>
<Form.Item>
{getFieldDecorator('appType', {})(
<Select style={{ width: '100%' }} placeholder="Select app type">
<Option value="ENTERPRISE">Enterprise</Option>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Row, Typography, Icon } from 'antd';
import { TeamOutlined } from '@ant-design/icons';
import { Row, Typography } from 'antd';
import StarRatings from 'react-star-ratings';
import './styles.css';
import axios from 'axios';
@ -118,7 +119,7 @@ class DetailedRating extends React.Component {
/>
<br />
<Text type="secondary" className="people-count">
<Icon type="team" /> {totalCount} total
<TeamOutlined /> {totalCount} total
</Text>
</div>
<div className="bar-containers">

@ -17,6 +17,7 @@
*/
import React from 'react';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { Modal, Button, Tag, List, Typography } from 'antd';
import pSBC from 'shade-blend-color';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
@ -47,7 +48,11 @@ class LifeCycleDetailsModal extends React.Component {
const { lifecycle } = this.props;
return (
<div>
<Button size="small" icon="question-circle" onClick={this.showModal}>
<Button
size="small"
icon={<QuestionCircleOutlined />}
onClick={this.showModal}
>
Learn more
</Button>
<Modal

@ -25,7 +25,6 @@ import {
Modal,
notification,
Steps,
Icon,
Alert,
Tabs,
} from 'antd';
@ -36,7 +35,7 @@ import './styles.css';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import { handleApiError } from '../../../../../../../../services/utils/errorHandler';
import LifeCycleHistory from './components/LifeCycleHistory';
import { EntgraIcon } from 'entgra-icons-react';
const { Text, Title, Paragraph } = Typography;
const { TabPane } = Tabs;
@ -216,7 +215,6 @@ class LifeCycle extends React.Component {
) {
proceedingStates = lifecycle[currentStatus].proceedingStates;
}
return (
<div>
<Title level={4}>Manage Lifecycle</Title>
@ -236,36 +234,38 @@ class LifeCycle extends React.Component {
onChange={this.onChange}
size="small"
>
{lifecycleSteps.map((step, index) => (
<Step
key={index}
icon={<Icon type={step.icon} />}
title={step.title}
disabled={current !== step.step}
description={
current === step.step && (
<div style={{ width: 400 }}>
<p>{step.text}</p>
{proceedingStates.map(lifecycleState => {
return (
<Button
size={'small'}
style={{ marginRight: 3 }}
onClick={() =>
this.showReasonModal(lifecycleState)
}
key={lifecycleState}
type={'primary'}
>
{lifecycleState}
</Button>
);
})}
</div>
)
}
/>
))}
{lifecycleSteps.map((step, index) => {
return (
<Step
key={index}
icon={<EntgraIcon type={step.icon} />}
title={step.title}
disabled={current !== step.step}
description={
current === step.step && (
<div style={{ width: 400 }}>
<p>{step.text}</p>
{proceedingStates.map(lifecycleState => {
return (
<Button
size={'small'}
style={{ marginRight: 3 }}
onClick={() =>
this.showReasonModal(lifecycleState)
}
key={lifecycleState}
type={'primary'}
>
{lifecycleState}
</Button>
);
})}
</div>
)
}
/>
);
})}
</Steps>
</div>
</TabPane>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Modal, Button, Icon, notification } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { Modal, Button, notification } from 'antd';
import axios from 'axios';
import { handleApiError } from '../../../../../../../../../../services/utils/errorHandler';
import { withConfigContext } from '../../../../../../../../../../components/ConfigContext';
@ -77,7 +78,7 @@ class DeleteRelease extends React.Component {
type="danger"
onClick={this.showModal}
>
<Icon type="delete" /> Delete
<DeleteOutlined /> Delete
</Button>
</>
);

@ -17,16 +17,22 @@
*/
import React from 'react';
import {
EditOutlined,
MinusOutlined,
PlusOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Modal,
Button,
Icon,
notification,
Spin,
Tooltip,
Upload,
Input,
Form,
Divider,
Row,
Col,
@ -397,7 +403,7 @@ class EditReleaseModal extends React.Component {
const config = this.props.context;
const uploadButton = (
<div>
<Icon type="plus" />
<PlusOutlined />
<div className="ant-upload-text">Select</div>
</div>
);
@ -417,7 +423,7 @@ class EditReleaseModal extends React.Component {
type="primary"
onClick={this.showModal}
>
<Icon type="edit" /> Edit
<EditOutlined /> Edit
</Button>
</Tooltip>
<Modal
@ -450,7 +456,7 @@ class EditReleaseModal extends React.Component {
>
{binaryFiles.length !== 1 && (
<Button>
<Icon type="upload" /> Change
<UploadOutlined /> Change
</Button>
)}
</Upload>,
@ -676,7 +682,7 @@ class EditReleaseModal extends React.Component {
<Button
type="dashed"
shape="circle"
icon="minus"
icon={<MinusOutlined />}
onClick={() => {
metaData.splice(index, 1);
this.setState({
@ -691,7 +697,7 @@ class EditReleaseModal extends React.Component {
})}
<Button
type="dashed"
icon="plus"
icon={<PlusOutlined />}
onClick={this.addNewMetaData}
>
Add

@ -63,7 +63,7 @@ class SingleReview extends React.Component {
);
return (
<div>
<div style={{ width: '100%' }}>
<List.Item.Meta
avatar={
<Avatar

@ -17,16 +17,8 @@
*/
import React from 'react';
import {
Divider,
Row,
Col,
Typography,
Button,
Icon,
Tooltip,
Alert,
} from 'antd';
import { ShopOutlined } from '@ant-design/icons';
import { Divider, Row, Col, Typography, Button, Tooltip, Alert } from 'antd';
import StarRatings from 'react-star-ratings';
import Reviews from './components/Reviews';
import '../../../../../../../../App.css';
@ -35,6 +27,7 @@ import EditRelease from './components/EditRelease';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
import Authorized from '../../../../../../../../components/Authorized/Authorized';
import DeleteRelease from './components/DeleteRelease';
import { EntgraIcon } from 'entgra-icons-react';
const { Title, Text, Paragraph } = Typography;
@ -64,12 +57,10 @@ class ReleaseView extends React.Component {
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let color = defaultPlatformIcons.default.color;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
color = defaultPlatformIcons[platform].color;
theme = defaultPlatformIcons[platform].theme;
}
let metaData = [];
try {
@ -98,7 +89,7 @@ class ReleaseView extends React.Component {
<br />
<Text>Platform : </Text>
<span style={{ fontSize: 20, color: color, textAlign: 'center' }}>
<Icon type={icon} theme={theme} />
<EntgraIcon type={icon} />
</span>
<Divider type="vertical" />
<Text>Version : {release.version}</Text>
@ -140,7 +131,7 @@ class ReleaseView extends React.Component {
style={{ float: 'right' }}
htmlType="button"
type="primary"
icon="shop"
icon={<ShopOutlined />}
disabled={!isAppInstallable}
onClick={() => {
window.open(
@ -161,7 +152,7 @@ class ReleaseView extends React.Component {
</Col>
</Row>
<Divider />
<Row className="release-images">
<Row className="release-images" style={{ flexFlow: 'nowrap' }}>
{release.screenshots.map((screenshotUrl, index) => {
return (
<div key={index} className="release-screenshot">

@ -17,11 +17,11 @@
*/
import React from 'react';
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
Card,
Tag,
message,
Icon,
Input,
notification,
Divider,
@ -157,11 +157,10 @@ class ManageCategories extends React.Component {
<>
<Divider type="vertical" />
<Tooltip title="edit">
<Icon
<EditOutlined
onClick={() => {
this.openEditModal(categoryName);
}}
type="edit"
/>
</Tooltip>
<Divider type="vertical" />
@ -182,7 +181,7 @@ class ManageCategories extends React.Component {
okText="Yes"
cancelText="No"
>
<Icon type="delete" />
<DeleteOutlined />
</Popconfirm>
</Tooltip>
</>
@ -399,7 +398,7 @@ class ManageCategories extends React.Component {
{!isAddNewVisible && (
<div style={{ float: 'right' }}>
<Button
icon="plus"
icon={<PlusOutlined />}
// type="primary"
size="small"
onClick={() => {
@ -456,7 +455,7 @@ class ManageCategories extends React.Component {
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<Icon type="plus" /> New Category
<PlusOutlined /> New Category
</Tag>
)}
</TweenOneGroup>

@ -17,11 +17,11 @@
*/
import React from 'react';
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
Card,
Tag,
message,
Icon,
Input,
notification,
Divider,
@ -151,11 +151,10 @@ class ManageTags extends React.Component {
<>
<Divider type="vertical" />
<Tooltip title="edit">
<Icon
<EditOutlined
onClick={() => {
this.openEditModal(tagName);
}}
type="edit"
/>
</Tooltip>
<Divider type="vertical" />
@ -176,7 +175,7 @@ class ManageTags extends React.Component {
okText="Yes"
cancelText="No"
>
<Icon type="delete" />
<DeleteOutlined />
</Popconfirm>
</Tooltip>
</>
@ -390,7 +389,7 @@ class ManageTags extends React.Component {
{!isAddNewVisible && (
<div style={{ float: 'right' }}>
<Button
icon="plus"
icon={<PlusOutlined />}
// type="primary"
size="small"
onClick={() => {
@ -447,7 +446,7 @@ class ManageTags extends React.Component {
onClick={this.showInput}
style={{ background: '#fff', borderStyle: 'dashed' }}
>
<Icon type="plus" /> New Tag
<PlusOutlined /> New Tag
</Tag>
)}
</TweenOneGroup>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { PageHeader, Typography, Breadcrumb, Row, Col, Icon } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Typography, Breadcrumb, Row, Col } from 'antd';
import ManageCategories from './components/Categories';
import ManageTags from './components/Tags';
import { Link } from 'react-router-dom';
@ -40,7 +41,7 @@ class Manage extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>

@ -18,13 +18,13 @@
import React from 'react';
import axios from 'axios';
import { DeleteOutlined, HomeOutlined } from '@ant-design/icons';
import {
Tag,
notification,
Table,
Typography,
Divider,
Icon,
Popconfirm,
Button,
} from 'antd';
@ -239,7 +239,7 @@ class Pages extends React.Component {
<Button
disabled={page.id === this.state.homePageId}
className="btn-warning"
icon="home"
icon={<HomeOutlined />}
type="link"
onClick={() => {
this.updateHomePage(page.id);
@ -259,7 +259,7 @@ class Pages extends React.Component {
>
<span className="action">
<Text type="danger">
<Icon type="delete" /> delete
<DeleteOutlined /> delete
</Text>
</span>
</Popconfirm>

@ -17,6 +17,7 @@
*/
import React from 'react';
import { SyncOutlined } from '@ant-design/icons';
import { Button, notification } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
@ -69,7 +70,7 @@ class SyncAndroidApps extends React.Component {
loading={loading}
style={{ marginTop: 16 }}
type="primary"
icon="sync"
icon={<SyncOutlined />}
>
Sync{loading && 'ing...'}
</Button>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { PageHeader, Breadcrumb, Divider, Icon, Result } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { PageHeader, Breadcrumb, Divider, Result } from 'antd';
import { Link } from 'react-router-dom';
import SyncAndroidApps from './components/SyncAndroidApps';
import { withConfigContext } from '../../../../../../components/ConfigContext';
@ -41,7 +42,7 @@ class ManageAndroidEnterprise extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Modal, Icon, Table, Avatar } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { Modal, Table, Avatar } from 'antd';
import '../../styles.css';
import { withConfigContext } from '../../../../../../../../../../../../components/ConfigContext';
@ -84,7 +85,7 @@ class AddAppsToClusterModal extends React.Component {
<div className="btn-add-new-wrapper">
<div className="btn-add-new">
<button className="btn" onClick={this.showModal}>
<Icon style={{ position: 'relative' }} type="plus" />
<PlusOutlined style={{ position: 'relative' }} />
</button>
</div>
<div className="title">Add app</div>

@ -17,11 +17,18 @@
*/
import React from 'react';
import {
CaretDownOutlined,
CaretLeftFilled,
CaretRightFilled,
CaretUpOutlined,
CloseCircleFilled,
DeleteOutlined,
} from '@ant-design/icons';
import {
Button,
Col,
Divider,
Icon,
message,
notification,
Popconfirm,
@ -304,7 +311,7 @@ class Cluster extends React.Component {
this.swapProduct(index, index - 1);
}}
>
<Icon type="caret-left" theme="filled" />
<CaretLeftFilled />
</button>
)}
</div>
@ -324,7 +331,7 @@ class Cluster extends React.Component {
}}
className="btn btn-right"
>
<Icon type="caret-right" theme="filled" />
<CaretRightFilled />
</button>
</div>
<div className="delete-btn">
@ -334,7 +341,7 @@ class Cluster extends React.Component {
this.removeProduct(index);
}}
>
<Icon type="close-circle" theme="filled" />
<CloseCircleFilled />
</button>
</div>
</>
@ -363,7 +370,7 @@ class Cluster extends React.Component {
<Tooltip title="Move Up">
<Button
type="link"
icon="caret-up"
icon={<CaretUpOutlined />}
size="large"
onClick={() => {
this.props.swapClusters(index, index - 1);
@ -374,7 +381,7 @@ class Cluster extends React.Component {
<Tooltip title="Move Down">
<Button
type="link"
icon="caret-down"
icon={<CaretDownOutlined />}
size="large"
onClick={() => {
this.props.swapClusters(index, index + 1);
@ -391,7 +398,7 @@ class Cluster extends React.Component {
>
<Button
type="danger"
icon="delete"
icon={<DeleteOutlined />}
shape="circle"
htmlType="button"
/>

@ -17,12 +17,12 @@
*/
import React from 'react';
import { HomeOutlined, PlusOutlined } from '@ant-design/icons';
import {
PageHeader,
Typography,
Breadcrumb,
Button,
Icon,
Col,
Row,
notification,
@ -326,7 +326,7 @@ class Page extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to="/publisher/apps">
<Icon type="home" /> Home
<HomeOutlined /> Home
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Manage</Breadcrumb.Item>
@ -406,7 +406,7 @@ class Page extends React.Component {
<Button
type="dashed"
shape="round"
icon="plus"
icon={<PlusOutlined />}
size="large"
onClick={() => {
this.toggleAddNewClusterVisibility(true);

@ -17,12 +17,13 @@
*/
import React from 'react';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import {
Typography,
Row,
Col,
Form,
Icon,
Input,
Button,
message,
@ -156,7 +157,7 @@ class NormalLoginForm extends React.Component {
})(
<Input
style={{ height: 32 }}
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
prefix={<UserOutlined style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
@ -167,7 +168,7 @@ class NormalLoginForm extends React.Component {
})(
<Input
style={{ height: 32 }}
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
prefix={<LockOutlined style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,

@ -69,7 +69,6 @@
<phase>generate-resources</phase>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<npmVersion>${npm.version}</npmVersion>
</configuration>
</execution>
<execution>

@ -11,7 +11,7 @@
"license": "Apache License 2.0",
"dependencies": {
"acorn": "^6.2.0",
"antd": "^3.23.6",
"antd": "^4.0.0",
"axios": "^0.18.1",
"babel-eslint": "^9.0.0",
"d3": "^5.9.7",

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Menu, Icon } from 'antd';
import { LogoutOutlined } from '@ant-design/icons';
import { Menu } from 'antd';
import axios from 'axios';
import { withConfigContext } from '../../../../components/context/ConfigContext';
import { handleApiError } from '../../../../services/utils/errorHandler';
@ -64,7 +65,7 @@ class Logout extends React.Component {
return (
<Menu>
<Menu.Item key="1" onClick={this.handleSubmit}>
<Icon type="logout" />
<LogoutOutlined />
Logout
</Menu.Item>
</Menu>

@ -17,7 +17,17 @@
*/
import React from 'react';
import { Layout, Menu, Icon, Drawer, Button, Alert } from 'antd';
import {
UploadOutlined,
UserOutlined,
AndroidFilled,
AppleFilled,
WindowsFilled,
HddFilled,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons';
import { Layout, Menu, Drawer, Button, Alert } from 'antd';
const { Header, Content, Footer } = Layout;
import { Link } from 'react-router-dom';
@ -112,23 +122,27 @@ class Dashboard extends React.Component {
};
render() {
const config = this.props.context;
const { selectedKeys, deviceTypes, forbiddenErrors } = this.state;
const DeviceTypesData = deviceTypes.map(deviceType => {
const platform = deviceType.name;
const defaultPlatformIcons = config.defaultPlatformIcons;
let icon = defaultPlatformIcons.default.icon;
let theme = defaultPlatformIcons.default.theme;
if (defaultPlatformIcons.hasOwnProperty(platform)) {
icon = defaultPlatformIcons[platform].icon;
theme = defaultPlatformIcons[platform].theme;
let icon;
switch (deviceType.name) {
case 'android':
icon = <AndroidFilled />;
break;
case 'ios':
icon = <AppleFilled />;
break;
case 'windows':
icon = <WindowsFilled />;
break;
default:
icon = <HddFilled />;
}
return (
<Menu.Item key={platform}>
<Link to={'/store/' + platform}>
<Icon type={icon} theme={theme} />
{platform}
<Menu.Item key={deviceType.name}>
<Link to={'/store/' + deviceType.name}>
{icon}
{deviceType.name}
</Link>
</Menu.Item>
);
@ -161,7 +175,7 @@ class Dashboard extends React.Component {
<Menu.Item key="web-clip">
<Link to="/store/web-clip">
<Icon type="upload" />
<UploadOutlined />
Web Clips
</Link>
</Menu.Item>
@ -170,7 +184,7 @@ class Dashboard extends React.Component {
className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
<UserOutlined />
{this.config.user.username}
</span>
}
@ -185,10 +199,11 @@ class Dashboard extends React.Component {
<Layout className="mobile-layout">
<div className="mobile-menu-button">
<Button type="link" onClick={this.showMobileNavigationBar}>
<Icon
type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'}
className="bar-icon"
/>
{this.state.collapsed ? (
<MenuFoldOutlined />
) : (
<MenuUnfoldOutlined />
)}
</Button>
</div>
</Layout>
@ -221,7 +236,7 @@ class Dashboard extends React.Component {
<Menu.Item key="web-clip">
<Link to="/store/web-clip">
<Icon type="upload" />
<UploadOutlined />
Web Clips
</Link>
</Menu.Item>
@ -236,7 +251,7 @@ class Dashboard extends React.Component {
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="user" />
<UserOutlined />
</span>
}
>

@ -143,11 +143,13 @@ class AppList extends React.Component {
>
<Row gutter={16}>
{apps.length === 0 && (
<Result
status="404"
title="No apps, yet."
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
/>
<Col span={24}>
<Result
status="404"
title="No apps, yet."
subTitle="No apps available, yet! When the administration uploads, apps will show up here."
/>
</Col>
)}
{apps.map(app => (
<Col key={app.id} xs={12} sm={6} md={6} lg={4} xl={3}>

@ -17,10 +17,10 @@
*/
import React from 'react';
import { StarOutlined } from '@ant-design/icons';
import {
Drawer,
Button,
Icon,
Row,
Col,
Typography,
@ -127,7 +127,7 @@ class AddReview extends React.Component {
return (
<div>
<Button type="primary" onClick={this.showDrawer}>
<Icon type="star" /> Add a review
<StarOutlined /> Add a review
</Button>
<Drawer

@ -17,7 +17,8 @@
*/
import React from 'react';
import { Row, Typography, Icon } from 'antd';
import { TeamOutlined } from '@ant-design/icons';
import { Row, Typography } from 'antd';
import StarRatings from 'react-star-ratings';
import './styles.css';
import { withConfigContext } from '../../../../../../../../../../../../../../components/context/ConfigContext';
@ -67,7 +68,7 @@ class Rating extends React.Component {
/>
<br />
<Text type="secondary" className="people-count">
<Icon type="team" /> {totalCount} total
<TeamOutlined /> {totalCount} total
</Text>
</div>
<div className="bar-containers">

@ -18,6 +18,7 @@
import React from 'react';
import axios from 'axios';
import { SyncOutlined } from '@ant-design/icons';
import { Tag, Table, Typography, Button, Alert } from 'antd';
import TimeAgo from 'javascript-time-ago';
@ -235,7 +236,7 @@ class SubscriptionDetails extends React.Component {
</Text>
</div>
<div style={{ textAlign: 'right', paddingBottom: 6 }}>
<Button icon="sync" onClick={this.fetch}>
<Button icon={<SyncOutlined />} onClick={this.fetch}>
Refresh
</Button>
</div>

@ -17,6 +17,7 @@
*/
import React from 'react';
import { DownOutlined } from '@ant-design/icons';
import {
Divider,
Row,
@ -26,7 +27,6 @@ import {
Dropdown,
notification,
Menu,
Icon,
Tabs,
Tag,
} from 'antd';
@ -216,13 +216,13 @@ class ReleaseView extends React.Component {
yes={
<Dropdown overlay={menu}>
<Button type="primary">
Subscribe <Icon type="down" />
Subscribe <DownOutlined />
</Button>
</Dropdown>
}
no={
<Button type="primary" disabled={true}>
Subscribe <Icon type="down" />
Subscribe <DownOutlined />
</Button>
}
/>

@ -18,7 +18,8 @@
import React from 'react';
import '../../../../../../../../App.css';
import { Skeleton, Typography, Row, Col, Card, Breadcrumb, Icon } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import { Skeleton, Typography, Row, Col, Card, Breadcrumb } from 'antd';
import ReleaseView from './components/ReleaseView';
import axios from 'axios';
import { withConfigContext } from '../../../../../../../../components/context/ConfigContext';
@ -110,7 +111,7 @@ class ReleasePage extends React.Component {
<Breadcrumb style={{ paddingBottom: 16 }}>
<Breadcrumb.Item>
<Link to={'/store/' + deviceType}>
<Icon type="home" /> {deviceType + ' apps'}{' '}
<HomeOutlined /> {deviceType + ' apps'}{' '}
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{appName}</Breadcrumb.Item>

@ -17,14 +17,14 @@
*/
import React from 'react';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import {
Typography,
Row,
Col,
Form,
Icon,
Input,
Button,
Form,
Checkbox,
notification,
} from 'antd';
@ -36,6 +36,62 @@ const { Title } = Typography;
const { Text } = Typography;
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false,
};
}
handleSubmit = values => {
this.setState({
loading: true,
inValid: false,
});
const parameters = {
username: values.username,
password: values.password,
platform: 'store',
};
const request = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
axios
.post(window.location.origin + '/store-ui-request-handler/login', request)
.then(res => {
if (res.status === 200) {
let redirectUrl = window.location.origin + '/store';
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has('redirect')) {
redirectUrl = searchParams.get('redirect');
}
window.location = redirectUrl;
} else {
throw new Error();
}
})
.catch(error => {
if (error.hasOwnProperty('response') && error.response.status === 401) {
this.setState({
loading: false,
inValid: true,
});
} else {
notification.error({
message: 'There was a problem',
duration: 10,
description: '',
});
this.setState({
loading: false,
inValid: false,
});
}
});
};
render() {
const config = this.props.context;
return (
@ -46,7 +102,13 @@ class Login extends React.Component {
<Col xs={3} sm={3} md={10}></Col>
<Col xs={18} sm={18} md={4}>
<Row style={{ marginBottom: 20 }}>
<Col style={{ textAlign: 'center' }}>
<Col
style={{
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
}}
>
<img
style={{
marginTop: 36,
@ -57,7 +119,58 @@ class Login extends React.Component {
</Col>
</Row>
<Title level={2}>Login</Title>
<WrappedNormalLoginForm />
<Form
initialValues={{ remember: true }}
onFinish={this.handleSubmit}
>
<Form.Item
name="username"
rules={[
{ required: true, message: 'Please input your username!' },
]}
>
<Input
name="username"
style={{ height: 32 }}
prefix={
<UserOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
}
placeholder="Username"
/>
</Form.Item>
<Form.Item
name="password"
rules={[
{ required: true, message: 'Please input your password!' },
]}
>
<Input.Password
prefix={
<LockOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
}
placeholder="Password"
/>
</Form.Item>
{this.state.loading && <Text type="secondary">Loading..</Text>}
{this.state.inValid && (
<Text type="danger">Invalid Login Details</Text>
)}
<br />
<a href="">Forgot password</a>
<Form.Item name="remember" valuePropName="checked">
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
block
loading={this.state.loading}
>
Log in
</Button>
</Form.Item>
</Form>
</Col>
</Row>
<Row>
@ -69,140 +182,4 @@ class Login extends React.Component {
}
}
class NormalLoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
inValid: false,
loading: false,
};
}
handleSubmit = e => {
const thisForm = this;
const config = this.props.context;
e.preventDefault();
this.props.form.validateFields((err, values) => {
thisForm.setState({
inValid: false,
});
if (!err) {
thisForm.setState({
loading: true,
});
const parameters = {
username: values.username,
password: values.password,
platform: 'store',
};
const request = Object.keys(parameters)
.map(key => key + '=' + parameters[key])
.join('&');
axios
.post(window.location.origin + config.serverConfig.loginUri, request)
.then(res => {
if (res.status === 200) {
let redirectUrl = window.location.origin + '/store';
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has('redirect')) {
redirectUrl = searchParams.get('redirect');
}
window.location = redirectUrl;
}
})
.catch(function(error) {
if (
error.hasOwnProperty('response') &&
error.response.status === 401
) {
thisForm.setState({
loading: false,
inValid: true,
});
} else {
notification.error({
message: 'There was a problem',
duration: 10,
description: '',
});
thisForm.setState({
loading: false,
inValid: false,
});
}
});
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
let errorMsg = '';
if (this.state.inValid) {
errorMsg = <Text type="danger">Invalid Login Details</Text>;
}
let loading = '';
if (this.state.loading) {
loading = <Text type="secondary">Loading..</Text>;
}
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
name="username"
style={{ height: 32 }}
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
name="password"
style={{ height: 32 }}
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
{loading}
{errorMsg}
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>Remember me</Checkbox>)}
<br />
<a className="login-form-forgot" href="">
Forgot password
</a>
<Button
loading={this.state.loading}
block
type="primary"
htmlType="submit"
className="login-form-button"
>
Log in
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedNormalLoginForm = withConfigContext(
Form.create({ name: 'normal_login' })(NormalLoginForm),
);
export default withConfigContext(Login);

@ -273,5 +273,9 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.wso2.carbon.multitenancy</groupId>
<artifactId>org.wso2.carbon.tenant.mgt</artifactId>
</dependency>
</dependencies>
</project>

@ -124,4 +124,4 @@
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
</web-app>

@ -2273,4 +2273,58 @@ public interface DeviceManagementService {
response = ErrorResponse.class)
})
Response getDeviceFilters();
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{clientId}/{clientSecret}/default-token")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting the default token",
notes = "Getting the default access token by using given client ID and the client secret value.",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:device:enroll")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully returned the default token details.",
response = Policy.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests.")}),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n " +
"Server error occurred while retrieving the default token.",
response = ErrorResponse.class)
}
)
Response getDefaultToken(
@ApiParam(
name = "client ID",
value = "Client Id.",
required = true)
@PathParam("clientId")
String clientId,
@ApiParam(
name = "client secret",
value = "Client Secret",
required = true)
@PathParam("clientSecret")
String clientSecret
);
}

@ -35,7 +35,6 @@
package org.wso2.carbon.device.mgt.jaxrs.service.api;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Info;
import io.swagger.annotations.ExtensionProperty;
@ -50,6 +49,7 @@ import io.swagger.annotations.ResponseHeader;
import org.apache.axis2.transport.http.HTTPConstants;
import org.wso2.carbon.apimgt.annotations.api.Scopes;
import org.wso2.carbon.apimgt.annotations.api.Scope;
import org.wso2.carbon.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation;
import org.wso2.carbon.device.mgt.jaxrs.beans.ActivityList;
import org.wso2.carbon.device.mgt.jaxrs.beans.BasicUserInfo;
import org.wso2.carbon.device.mgt.jaxrs.beans.BasicUserInfoList;
@ -75,7 +75,6 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@SwaggerDefinition(
info = @Info(
@ -894,7 +893,8 @@ public interface UserManagementService {
@ApiParam(
name = "users",
value = "List of users",
required = true) List<String> usernames);
required = true)
@Valid DeviceEnrollmentInvitation deviceEnrollmentInvitation);
@POST
@Path("/enrollment-invite")

@ -36,6 +36,7 @@ import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.InvalidConfigurationException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.ComplianceFeature;
import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.PolicyComplianceException;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
@ -50,6 +51,9 @@ import org.wso2.carbon.event.stream.stub.EventStreamAdminServiceStub;
import org.wso2.carbon.event.stream.stub.types.EventStreamAttributeDto;
import org.wso2.carbon.event.stream.stub.types.EventStreamDefinitionDto;
import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException;
import org.wso2.carbon.policy.mgt.common.PolicyAdministratorPoint;
import org.wso2.carbon.policy.mgt.common.PolicyEvaluationException;
import org.wso2.carbon.policy.mgt.common.PolicyManagementException;
import org.wso2.carbon.user.api.UserStoreException;
import javax.validation.Valid;
@ -95,6 +99,10 @@ public class DeviceAgentServiceImpl implements DeviceAgentService {
device.getEnrolmentInfo().setDateOfEnrolment(System.currentTimeMillis());
device.getEnrolmentInfo().setDateOfLastUpdate(System.currentTimeMillis());
boolean status = dms.enrollDevice(device);
PolicyAdministratorPoint pap = DeviceMgtAPIUtils.getPolicyManagementService().getPAP();
DeviceIdentifier deviceId = new DeviceIdentifier(device.getDeviceIdentifier(), device.getType());
DeviceMgtAPIUtils.getPolicyManagementService().getEffectivePolicy(deviceId);
pap.publishChanges();
return Response.status(Response.Status.OK).entity(status).build();
} catch (DeviceManagementException e) {
String msg = "Error occurred while enrolling the device, which carries the id '" +
@ -104,6 +112,9 @@ public class DeviceAgentServiceImpl implements DeviceAgentService {
} catch (InvalidConfigurationException e) {
log.error("failed to add operation", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
} catch (PolicyManagementException e) {
log.error("failed to add designated policies against newly enrolled device.", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}

@ -38,11 +38,13 @@ package org.wso2.carbon.device.mgt.jaxrs.service.impl;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceFilters;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
@ -102,6 +104,10 @@ import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceManagementService;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.InputValidationException;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil;
import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils;
import org.wso2.carbon.identity.jwt.client.extension.JWTClient;
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException;
import org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService;
import org.wso2.carbon.policy.mgt.common.PolicyManagementException;
import org.wso2.carbon.policy.mgt.core.PolicyManagerService;
import org.wso2.carbon.user.api.UserStoreException;
@ -1282,6 +1288,10 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
operation.setId(operationStatusBean.getOperationId());
DeviceMgtAPIUtils.getDeviceManagementService().updateOperation(device, operation);
return Response.status(Response.Status.OK).entity("OperationStatus updated successfully.").build();
} catch (BadRequestException e) {
String msg = "Error occured due to invalid request";
log.error(msg, e);
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
} catch (DeviceManagementException e) {
String msg = "Error occurred when fetching device " + deviceIdentifier.toString();
log.error(msg, e);
@ -1290,10 +1300,6 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
String msg = "Error occurred when updating operation of device " + deviceIdentifier;
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (BadRequestException e) {
String msg = "Error occured due to invalid request";
log.error(msg, e);
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
}
}
@ -1328,4 +1334,21 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
}
}
@GET
@Path("/{clientId}/{clientSecret}/default-token")
@Override
public Response getDefaultToken(@PathParam("clientId") String clientId, @PathParam("clientSecret") String clientSecret) {
JWTClientManagerService jwtClientManagerService = DeviceMgtAPIUtils.getJWTClientManagerService();
try {
JWTClient jwtClient = jwtClientManagerService.getJWTClient();
AccessTokenInfo accessTokenInfo = jwtClient.getAccessToken(clientId, clientSecret,
PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(), "default");
return Response.status(Response.Status.OK).entity(accessTokenInfo).build();
} catch (JWTClientException e) {
String msg = "Error occurred while getting default access token by using given client Id and client secret.";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
}
}
}

@ -25,15 +25,19 @@ import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.GroupPaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException;
import org.wso2.carbon.device.mgt.common.group.mgt.DeviceGroup;
import org.wso2.carbon.device.mgt.common.group.mgt.DeviceGroupConstants;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupAlreadyExistException;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupManagementException;
import org.wso2.carbon.device.mgt.common.group.mgt.RoleDoesNotExistException;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupNotExistException;
import org.wso2.carbon.device.mgt.common.group.mgt.RoleDoesNotExistException;
import org.wso2.carbon.device.mgt.common.policy.mgt.Policy;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.GroupManagementProviderService;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceGroupList;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList;
@ -42,6 +46,10 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.RoleList;
import org.wso2.carbon.device.mgt.jaxrs.service.api.GroupManagementService;
import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil;
import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils;
import org.wso2.carbon.policy.mgt.common.PolicyAdministratorPoint;
import org.wso2.carbon.policy.mgt.common.PolicyEvaluationException;
import org.wso2.carbon.policy.mgt.common.PolicyEvaluationPoint;
import org.wso2.carbon.policy.mgt.common.PolicyManagementException;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
@ -259,6 +267,16 @@ public class GroupManagementServiceImpl implements GroupManagementService {
public Response addDevicesToGroup(int groupId, List<DeviceIdentifier> deviceIdentifiers) {
try {
DeviceMgtAPIUtils.getGroupManagementProviderService().addDevices(groupId, deviceIdentifiers);
PolicyAdministratorPoint pap = DeviceMgtAPIUtils.getPolicyManagementService().getPAP();
DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService();
for(DeviceIdentifier deviceIdentifier : deviceIdentifiers) {
Device device = dms.getDevice(deviceIdentifier, false);
if(!device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.REMOVED)) {
pap.removePolicyUsed(deviceIdentifier);
DeviceMgtAPIUtils.getPolicyManagementService().getEffectivePolicy(deviceIdentifier);
}
}
pap.publishChanges();
return Response.status(Response.Status.OK).build();
} catch (GroupManagementException e) {
String msg = "Error occurred while adding devices to group.";
@ -266,6 +284,15 @@ public class GroupManagementServiceImpl implements GroupManagementService {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (DeviceNotFoundException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
} catch (PolicyManagementException e) {
log.error("Error occurred while adding policies against device(s).", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
// } catch (PolicyEvaluationException e) {
// log.error("Error occurred while retrieving policies against device(s).", e);
// return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
} catch (DeviceManagementException e) {
log.error("Error occurred while retrieving device information.", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
@ -273,6 +300,16 @@ public class GroupManagementServiceImpl implements GroupManagementService {
public Response removeDevicesFromGroup(int groupId, List<DeviceIdentifier> deviceIdentifiers) {
try {
DeviceMgtAPIUtils.getGroupManagementProviderService().removeDevice(groupId, deviceIdentifiers);
PolicyAdministratorPoint pap = DeviceMgtAPIUtils.getPolicyManagementService().getPAP();
DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService();
for(DeviceIdentifier deviceIdentifier : deviceIdentifiers) {
Device device = dms.getDevice(deviceIdentifier, false);
if(!device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.REMOVED)) {
pap.removePolicyUsed(deviceIdentifier);
DeviceMgtAPIUtils.getPolicyManagementService().getEffectivePolicy(deviceIdentifier);
}
}
pap.publishChanges();
return Response.status(Response.Status.OK).build();
} catch (GroupManagementException e) {
String msg = "Error occurred while removing devices from group.";
@ -280,6 +317,12 @@ public class GroupManagementServiceImpl implements GroupManagementService {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (DeviceNotFoundException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
}catch (PolicyManagementException e) {
log.error("Error occurred while adding policies against device(s).", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}catch (DeviceManagementException e) {
log.error("Error occurred while retrieving device information.", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
@ -290,6 +333,8 @@ public class GroupManagementServiceImpl implements GroupManagementService {
deviceIdentifiers.add(deviceToGroupsAssignment.getDeviceIdentifier());
GroupManagementProviderService service = DeviceMgtAPIUtils.getGroupManagementProviderService();
List<DeviceGroup> deviceGroups = service.getGroups(deviceToGroupsAssignment.getDeviceIdentifier(), false);
PolicyAdministratorPoint pap = DeviceMgtAPIUtils.getPolicyManagementService().getPAP();
DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService();
for (DeviceGroup group : deviceGroups) {
Integer groupId = group.getGroupId();
if (deviceToGroupsAssignment.getDeviceGroupIds().contains(groupId)) {
@ -300,7 +345,12 @@ public class GroupManagementServiceImpl implements GroupManagementService {
}
for (int groupId : deviceToGroupsAssignment.getDeviceGroupIds()) {
DeviceMgtAPIUtils.getGroupManagementProviderService().addDevices(groupId, deviceIdentifiers);
for (DeviceIdentifier deviceIdentifier : deviceIdentifiers) {
pap.removePolicyUsed(deviceIdentifier);
DeviceMgtAPIUtils.getPolicyManagementService().getEffectivePolicy(deviceIdentifier);
}
}
pap.publishChanges();
return Response.status(Response.Status.OK).build();
} catch (GroupManagementException e) {
String msg = "Error occurred while assigning device to groups.";
@ -308,6 +358,9 @@ public class GroupManagementServiceImpl implements GroupManagementService {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (DeviceNotFoundException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
} catch (PolicyManagementException e) {
log.error("Failed to add policies for device assigned to group.", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
@ -325,4 +378,4 @@ public class GroupManagementServiceImpl implements GroupManagementService {
}
}
}
}

@ -32,7 +32,14 @@ import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil;
import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils;
import javax.validation.constraints.Size;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;

@ -45,8 +45,11 @@ import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.OTPManagementException;
import org.wso2.carbon.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation;
import org.wso2.carbon.device.mgt.common.operation.mgt.Activity;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.EmailMetaInfo;
@ -716,48 +719,29 @@ public class UserManagementServiceImpl implements UserManagementService {
return CredentialManagementResponseBuilder.buildChangePasswordResponse(credentials);
}
/**
* Method used to send an invitation email to a existing user to enroll a device.
*
* @param usernames Username list of the users to be invited
*/
@POST
@Path("/send-invitation")
@Produces({MediaType.APPLICATION_JSON})
public Response inviteExistingUsersToEnrollDevice(List<String> usernames) {
public Response inviteExistingUsersToEnrollDevice(DeviceEnrollmentInvitation deviceEnrollmentInvitation) {
if (deviceEnrollmentInvitation.getUsernames() == null || deviceEnrollmentInvitation.getUsernames().isEmpty()) {
String msg = "Error occurred while validating list of user-names. User-names cannot be empty.";
log.error(msg);
throw new BadRequestException(
new ErrorResponse.ErrorResponseBuilder().setCode(HttpStatus.SC_BAD_REQUEST).setMessage(msg)
.build());
}
if (log.isDebugEnabled()) {
log.debug("Sending enrollment invitation mail to existing user.");
log.debug("Sending device enrollment invitation mail to existing user/s.");
}
DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService();
OTPManagementService oms = DeviceMgtAPIUtils.getOTPManagementService();
try {
for (String username : usernames) {
String recipient = getClaimValue(username, Constants.USER_CLAIM_EMAIL_ADDRESS);
Properties props = new Properties();
props.setProperty("first-name", getClaimValue(username, Constants.USER_CLAIM_FIRST_NAME));
props.setProperty("username", username);
EmailMetaInfo metaInfo = new EmailMetaInfo(recipient, props);
dms.sendEnrolmentInvitation(DeviceManagementConstants.EmailAttributes.USER_ENROLLMENT_TEMPLATE,
metaInfo);
}
} catch (DeviceManagementException e) {
String msg = "Error occurred while inviting user to enrol their device";
if (e.getMessage() != null && !e.getMessage().isEmpty()) {
msg = e.getMessage();
}
oms.sendDeviceEnrollmentInvitationMail(deviceEnrollmentInvitation);
} catch (OTPManagementException e) {
String msg = "Error occurred while generating OTP and inviting user/s to enroll their device/s.";
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
} catch (UserStoreException e) {
String msg = "Error occurred while getting claim values to invite user";
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
} catch (ConfigurationManagementException e) {
String msg = "Error occurred while sending the email invitations. Mail server not configured.";
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
return Response.status(Response.Status.OK).entity("Invitation mails have been sent.").build();
}

@ -14,6 +14,23 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*
* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.jaxrs.util;
@ -52,6 +69,7 @@ import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService;
import org.wso2.carbon.device.mgt.common.report.mgt.ReportManagementService;
import org.wso2.carbon.device.mgt.common.spi.DeviceTypeGeneratorService;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService;
import org.wso2.carbon.device.mgt.core.device.details.mgt.DeviceInformationManager;
import org.wso2.carbon.device.mgt.core.dto.DeviceTypeVersion;
@ -134,13 +152,14 @@ public class DeviceMgtAPIUtils {
public static final String DAS_ADMIN_SERVICE_EP = "https://" + DAS_HOST_NAME + ":" + DAS_PORT + "/services/";
private static SSLContext sslContext;
private static Log log = LogFactory.getLog(DeviceMgtAPIUtils.class);
private static final Log log = LogFactory.getLog(DeviceMgtAPIUtils.class);
private static KeyStore keyStore;
private static KeyStore trustStore;
private static char[] keyStorePassword;
private static IntegrationClientService integrationClientService;
private static MetadataManagementService metadataManagementService;
private static OTPManagementService otpManagementService;
static {
String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Password");
@ -337,6 +356,25 @@ public class DeviceMgtAPIUtils {
return integrationClientService;
}
/**
* Initializing and accessing method for OTPManagementService.
*
* @return OTPManagementService instance
* @throws IllegalStateException if OTPManagementService cannot be initialized
*/
public static synchronized OTPManagementService getOTPManagementService() {
if (otpManagementService == null) {
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
otpManagementService = (OTPManagementService) ctx.getOSGiService(OTPManagementService.class, null);
if (otpManagementService == null) {
String msg = "OTP Management service has not initialized.";
log.error(msg);
throw new IllegalStateException(msg);
}
}
return otpManagementService;
}
public static RegistryService getRegistryService() {
RegistryService registryService;
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();

@ -93,6 +93,7 @@ public class DeviceAgentServiceTest {
private static final String AUTHENTICATED_USER = "admin";
private static final String MONITOR_OPERATION = "POLICY_MONITOR";
private static Device demoDevice;
private PolicyManagerService policyManagerService;
@ObjectFactory
public IObjectFactory getObjectFactory() {
@ -108,6 +109,7 @@ public class DeviceAgentServiceTest {
this.deviceAgentService = new DeviceAgentServiceImpl();
this.deviceAccessAuthorizationService = Mockito.mock(DeviceAccessAuthorizationServiceImpl.class,
Mockito.RETURNS_MOCKS);
this.policyManagerService = Mockito.mock(PolicyManagerService.class, Mockito.RETURNS_MOCKS);
this.privilegedCarbonContext = Mockito.mock(PrivilegedCarbonContext.class, Mockito.RETURNS_MOCKS);
this.eventStreamAdminServiceStub = Mockito.mock(EventStreamAdminServiceStub.class, Mockito.RETURNS_MOCKS);
demoDevice = DeviceMgtAPITestHelper.generateDummyDevice(TEST_DEVICE_TYPE, TEST_DEVICE_IDENTIFIER);
@ -167,6 +169,8 @@ public class DeviceAgentServiceTest {
.toReturn(this.deviceManagementProviderService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getAuthenticatedUser"))
.toReturn(AUTHENTICATED_USER);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getPolicyManagementService"))
.toReturn(policyManagerService);
EnrolmentInfo enrolmentInfo = demoDevice.getEnrolmentInfo();
enrolmentInfo.setStatus(EnrolmentInfo.Status.INACTIVE);
demoDevice.setEnrolmentInfo(enrolmentInfo);

@ -19,6 +19,7 @@
package org.wso2.carbon.device.mgt.jaxrs.service.impl;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
@ -42,10 +43,12 @@ import org.wso2.carbon.device.mgt.common.group.mgt.GroupAlreadyExistException;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupManagementException;
import org.wso2.carbon.device.mgt.common.group.mgt.GroupNotExistException;
import org.wso2.carbon.device.mgt.common.group.mgt.RoleDoesNotExistException;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.GroupManagementProviderService;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceToGroupsAssignment;
import org.wso2.carbon.device.mgt.jaxrs.service.api.GroupManagementService;
import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils;
import org.wso2.carbon.policy.mgt.core.PolicyManagerService;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
@ -61,6 +64,8 @@ import java.util.List;
public class GroupManagementServiceImplTest {
private GroupManagementService groupManagementService;
private GroupManagementProviderService groupManagementProviderService;
private PolicyManagerService policyManagerService;
private DeviceManagementProviderService deviceManagementProviderService;
private PrivilegedCarbonContext context;
@ObjectFactory
@ -72,6 +77,8 @@ public class GroupManagementServiceImplTest {
public void init() {
groupManagementService = new GroupManagementServiceImpl();
groupManagementProviderService = Mockito.mock(GroupManagementProviderService.class);
this.policyManagerService = Mockito.mock(PolicyManagerService.class, Mockito.RETURNS_MOCKS);
this.deviceManagementProviderService = Mockito.mock(DeviceManagementProviderService.class, Mockito.RETURNS_MOCKS);
context = Mockito.mock(PrivilegedCarbonContext.class);
Mockito.doReturn("admin").when(context).getUsername();
}
@ -298,6 +305,10 @@ public class GroupManagementServiceImplTest {
public void testAddDevicesToGroup() throws GroupManagementException, DeviceNotFoundException {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getGroupManagementProviderService"))
.toReturn(groupManagementProviderService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getPolicyManagementService"))
.toReturn(policyManagerService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(deviceManagementProviderService);
List<DeviceIdentifier> deviceIdentifiers = new ArrayList<>();
Mockito.doNothing().when(groupManagementProviderService).addDevices(1, deviceIdentifiers);
Mockito.doThrow(new GroupManagementException()).when(groupManagementProviderService).addDevices(2,
@ -319,6 +330,10 @@ public class GroupManagementServiceImplTest {
public void testRemoveDevicesFromGroup() throws GroupManagementException, DeviceNotFoundException {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getGroupManagementProviderService"))
.toReturn(groupManagementProviderService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getPolicyManagementService"))
.toReturn(policyManagerService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(deviceManagementProviderService);
List<DeviceIdentifier> deviceIdentifiers = new ArrayList<>();
Mockito.doNothing().when(groupManagementProviderService).removeDevice(1, deviceIdentifiers);
Mockito.doThrow(new GroupManagementException()).when(groupManagementProviderService).removeDevice(2,
@ -357,6 +372,10 @@ public class GroupManagementServiceImplTest {
public void testUpdateDeviceAssigningToGroups() throws GroupManagementException, DeviceNotFoundException {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getGroupManagementProviderService"))
.toReturn(groupManagementProviderService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getPolicyManagementService"))
.toReturn(policyManagerService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(deviceManagementProviderService);
Mockito.reset(groupManagementProviderService);
DeviceToGroupsAssignment deviceToGroupsAssignment = new DeviceToGroupsAssignment();
List<Integer> groupIds = new ArrayList<>();

@ -15,6 +15,23 @@
* specific language governing permissions and limitations
* under the License.
*
*
* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*
*/
package org.wso2.carbon.device.mgt.jaxrs.service.impl;
@ -34,6 +51,10 @@ import org.testng.annotations.Test;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.OTPManagementException;
import org.wso2.carbon.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.otp.mgt.service.OTPManagementServiceImpl;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderServiceImpl;
import org.wso2.carbon.device.mgt.jaxrs.beans.BasicUserInfo;
@ -66,9 +87,11 @@ public class UserManagementServiceImplTest {
private UserStoreManager userStoreManager;
private UserManagementService userManagementService;
private DeviceManagementProviderService deviceManagementProviderService;
private OTPManagementService otpManagementService;
private static final String DEFAULT_DEVICE_USER = "Internal/devicemgt-user";
private UserRealm userRealm;
private EnrollmentInvitation enrollmentInvitation;
private DeviceEnrollmentInvitation deviceEnrollmentInvitation;
private List<String> userList;
private static final String TEST_USERNAME = "test";
private static final String TEST2_USERNAME = "test2";
@ -86,6 +109,7 @@ public class UserManagementServiceImplTest {
userStoreManager = Mockito.mock(UserStoreManager.class, Mockito.RETURNS_MOCKS);
deviceManagementProviderService = Mockito
.mock(DeviceManagementProviderServiceImpl.class, Mockito.CALLS_REAL_METHODS);
otpManagementService = Mockito.mock(OTPManagementServiceImpl.class, Mockito.CALLS_REAL_METHODS);
userRealm = Mockito.mock(UserRealm.class);
RealmConfiguration realmConfiguration = Mockito.mock(RealmConfiguration.class);
Mockito.doReturn(null).when(realmConfiguration).getSecondaryRealmConfig();
@ -97,6 +121,8 @@ public class UserManagementServiceImplTest {
enrollmentInvitation.setRecipients(recipients);
userList = new ArrayList<>();
userList.add(TEST_USERNAME);
deviceEnrollmentInvitation = new DeviceEnrollmentInvitation();
deviceEnrollmentInvitation.setUsernames(userList);
}
@Test(description = "This method tests the addUser method of UserManagementService")
@ -205,13 +231,11 @@ public class UserManagementServiceImplTest {
@Test(description = "This method tests the send invitation method of UserManagementService", dependsOnMethods =
{"testIsUserExists"})
public void testSendInvitation() throws ConfigurationManagementException, DeviceManagementException {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getUserStoreManager"))
.toReturn(this.userStoreManager);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(this.deviceManagementProviderService);
Mockito.doNothing().when(deviceManagementProviderService).sendEnrolmentInvitation(Mockito.any(), Mockito.any());
Response response = userManagementService.inviteExistingUsersToEnrollDevice(userList);
public void testSendInvitation() throws OTPManagementException {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getOTPManagementService"))
.toReturn(this.otpManagementService);
Mockito.doNothing().when(otpManagementService).sendDeviceEnrollmentInvitationMail(Mockito.any());
Response response = userManagementService.inviteExistingUsersToEnrollDevice(deviceEnrollmentInvitation);
Assert.assertEquals(response.getStatus(), Response.Status.OK.getStatusCode(),
"Inviting existing users to enroll device failed");
}
@ -240,7 +264,7 @@ public class UserManagementServiceImplTest {
@Test(description = "This method tests the inviteToEnrollDevice method of UserManagementService",
dependsOnMethods = "testGetUsers")
public void testInviteToEnrollDevice() {
public void testInviteToEnrollDevice() throws ConfigurationManagementException, DeviceManagementException {
URL resourceUrl = ClassLoader.getSystemResource("testng.xml");
System.setProperty("carbon.home", resourceUrl.getPath());
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getUserStoreManager"))
@ -248,6 +272,7 @@ public class UserManagementServiceImplTest {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getAuthenticatedUser")).toReturn(TEST_USERNAME);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(this.deviceManagementProviderService);
Mockito.doNothing().when(deviceManagementProviderService).sendEnrolmentInvitation(Mockito.any(), Mockito.any());
EnrollmentInvitation enrollmentInvitation = new EnrollmentInvitation();
List<String> recipients = new ArrayList<>();
recipients.add(TEST_USERNAME);
@ -289,16 +314,22 @@ public class UserManagementServiceImplTest {
@Test(description = "This method tests the behaviour of methods when there is an issue with "
+ "DeviceManagementProviderService", dependsOnMethods = {"testGetUserCount"})
public void testNegativeScenarios1() throws ConfigurationManagementException, DeviceManagementException {
public void testNegativeScenarios1()
throws ConfigurationManagementException, DeviceManagementException, OTPManagementException {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getUserStoreManager"))
.toReturn(this.userStoreManager);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(this.deviceManagementProviderService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getAuthenticatedUser")).toReturn(TEST_USERNAME);
Mockito.reset(deviceManagementProviderService);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getOTPManagementService"))
.toReturn(this.otpManagementService);
Mockito.reset(otpManagementService);
Mockito.doThrow(new DeviceManagementException()).when(deviceManagementProviderService)
.sendEnrolmentInvitation(Mockito.any(), Mockito.any());
Response response = userManagementService.inviteExistingUsersToEnrollDevice(userList);
Mockito.doThrow(new OTPManagementException()).when(otpManagementService)
.sendDeviceEnrollmentInvitationMail(Mockito.any());
Response response = userManagementService.inviteExistingUsersToEnrollDevice(deviceEnrollmentInvitation);
Assert.assertEquals(response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
"Invite existing users to enroll device succeeded under erroneous conditions");
response = userManagementService.inviteToEnrollDevice(enrollmentInvitation);
@ -346,6 +377,8 @@ public class UserManagementServiceImplTest {
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getDeviceManagementService"))
.toReturn(this.deviceManagementProviderService);
Mockito.reset(this.userStoreManager);
PowerMockito.stub(PowerMockito.method(DeviceMgtAPIUtils.class, "getOTPManagementService"))
.toReturn(this.otpManagementService);
Mockito.doThrow(new UserStoreException()).when(userStoreManager)
.getUserClaimValue(Mockito.any(), Mockito.any(), Mockito.any());
Mockito.doThrow(new UserStoreException()).when(userStoreManager)
@ -362,7 +395,7 @@ public class UserManagementServiceImplTest {
response = userManagementService.inviteToEnrollDevice(enrollmentInvitation);
Assert.assertEquals(response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
"Invite existing users to enroll device succeeded under erroneous conditions");
response = userManagementService.inviteExistingUsersToEnrollDevice(userList);
response = userManagementService.inviteExistingUsersToEnrollDevice(deviceEnrollmentInvitation);
Assert.assertEquals(response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
"Invite existing users to enroll device succeeded under erroneous conditions");
}

@ -125,4 +125,14 @@ public final class DeviceManagementConstants {
public static final String DEFAULT_HTTP_PROTOCOL = "https";
public static final String DAS_URL = DEFAULT_HTTP_PROTOCOL + "://" + DAS_HOST_NAME + ":" + DAS_PORT;
}
public static final class OTPProperties {
private OTPProperties() { throw new AssertionError(); }
public static final String FIRST_NAME = "first-name";
public static final String LAST_NAME = "last-name";
public static final String TENANT_ADMIN_USERNAME = "tenant-admin-username";
public static final String TENANT_ADMIN_PASSWORD = "tenant-admin-password";
}
}

@ -19,7 +19,7 @@
package org.wso2.carbon.device.mgt.common.exceptions;
public class BadRequestException extends Exception {
public class BadRequestException extends DeviceManagementException {
private static final long serialVersionUID = 2304023531260840549L;
public BadRequestException() {

@ -0,0 +1,33 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.exceptions;
/**
* Exception thrown due to Database Connection issues.
*/
public class DBConnectionException extends Exception {
private static final long serialVersionUID = -6779125067467878014L;
public DBConnectionException(String message, Throwable cause) {
super(message, cause);
}
public DBConnectionException(String msg) {
super(msg);
}
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.
*/
package org.wso2.carbon.device.mgt.common.exceptions;
public class OTPManagementException extends Exception {
private static final long serialVersionUID = 397485329551276175L;
public OTPManagementException(String msg, Exception nestedEx) {
super(msg, nestedEx);
}
public OTPManagementException(String message, Throwable cause) {
super(message, cause);
}
public OTPManagementException(String msg) {
super(msg);
}
public OTPManagementException() {
super();
}
public OTPManagementException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,45 @@
/*
* 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.
*
*/
package org.wso2.carbon.device.mgt.common.exceptions;
public class UnAuthorizedException extends DeviceManagementException {
private static final long serialVersionUID = 2304023531260840549L;
public UnAuthorizedException() {
super();
}
public UnAuthorizedException(String msg) {
super(msg);
}
public UnAuthorizedException(Throwable cause) {
super(cause);
}
public UnAuthorizedException(String msg, Exception nestedEx) {
super(msg, nestedEx);
}
public UnAuthorizedException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,96 @@
/*
* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.general;
import java.util.Map;
public class OneTimeTokenDetails extends TenantDetail {
String password;
String token;
long createdDate;
long updatedDate;
boolean isExpired;
String metaInfo;
Map<String, String> replaceValue;
String emailType;
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
public long getUpdatedDate() {
return updatedDate;
}
public void setUpdatedDate(long updatedDate) {
this.updatedDate = updatedDate;
}
public boolean isExpired() {
return isExpired;
}
public void setExpired(boolean expired) {
isExpired = expired;
}
public String getMetaInfo() {
return metaInfo;
}
public void setMetaInfo(String metaInfo) {
this.metaInfo = metaInfo;
}
public Map<String, String> getReplaceValue() {
return replaceValue;
}
public void setReplaceValue(Map<String, String> replaceValue) {
this.replaceValue = replaceValue;
}
public String getEmailType() {
return emailType;
}
public void setEmailType(String emailType) {
this.emailType = emailType;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

@ -0,0 +1,59 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.invitation.mgt;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
@ApiModel(
value = "DeviceEnrollmentInvitation",
description = "Holds data to send device enrollment invitation to list of existing users.")
public class DeviceEnrollmentInvitation implements Serializable {
private static final long serialVersionUID = 6933837278652532052L;
@ApiModelProperty(
name = "usernames",
value = "List of usernames of users.",
required = true)
private List<String> usernames;
@ApiModelProperty(
name = "deviceEnrollmentTypes",
value = "List of enrollment types against device types.")
private List<DeviceEnrollmentType> deviceEnrollmentTypes;
public List<String> getUsernames() {
return usernames;
}
public void setUsernames(List<String> usernames) {
this.usernames = usernames;
}
public List<DeviceEnrollmentType> getDeviceEnrollmentTypes() {
return deviceEnrollmentTypes;
}
public void setDeviceEnrollmentTypes(
List<DeviceEnrollmentType> deviceEnrollmentTypes) {
this.deviceEnrollmentTypes = deviceEnrollmentTypes;
}
}

@ -0,0 +1,25 @@
package org.wso2.carbon.device.mgt.common.invitation.mgt;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "DeviceEnrollmentInvitationDetails", propOrder = {
"enrollmentDetails"
})
public class DeviceEnrollmentInvitationDetails {
@XmlElement(name = "EnrollmentDetails")
private List<EnrollmentDetails> enrollmentDetails;
public List<EnrollmentDetails> getEnrollmentDetails() {
return enrollmentDetails;
}
public void setEnrollmentDetails(List<EnrollmentDetails> enrollmentDetails) {
this.enrollmentDetails = enrollmentDetails;
}
}

@ -0,0 +1,59 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.invitation.mgt;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
@ApiModel(
value = "DeviceEnrollmentType",
description = "Holds data of enrollment types against device types.")
public class DeviceEnrollmentType implements Serializable {
private static final long serialVersionUID = 6563596191450032613L;
@ApiModelProperty(
name = "deviceType",
value = "Device type (i.e: android, ios, windows)",
required = true)
private String deviceType;
@ApiModelProperty(
name = "enrollmentType",
value = "Enrollment type (i.e: BYOD, COPE, COSU)",
required = true)
private List<String> enrollmentType;
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public List<String> getEnrollmentType() {
return enrollmentType;
}
public void setEnrollmentType(List<String> enrollmentType) {
this.enrollmentType = enrollmentType;
}
}

@ -0,0 +1,36 @@
package org.wso2.carbon.device.mgt.common.invitation.mgt;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EnrollmentDetails", propOrder = {
"enrollmentType",
"enrollmentSteps"
})
public class EnrollmentDetails {
@XmlElement(name = "EnrollmentType")
private String enrollmentType;
@XmlElement(name = "EnrollmentSteps")
private String enrollmentSteps;
public String getEnrollmentType() {
return enrollmentType;
}
public void setEnrollmentType(String enrollmentType) {
this.enrollmentType = enrollmentType;
}
public String getEnrollmentSteps() {
return enrollmentSteps;
}
public void setEnrollmentSteps(String enrollmentSteps) {
this.enrollmentSteps = enrollmentSteps;
}
}

@ -102,5 +102,4 @@ public interface NotificationManagementService {
PaginationResult getNotificationsByStatus(Notification.Status status,
PaginationRequest request) throws NotificationManagementException;
}

@ -0,0 +1,22 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.otp.mgt;
public enum OTPEmailTypes {
USER_VERIFY, DEVICE_ENROLLMENT
}

@ -0,0 +1,113 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.otp.mgt.dto;
import java.sql.Timestamp;
public class OneTimePinDTO {
int id;
String otpToken;
int tenantId;
String username;
String email;
String emailType;
String metaInfo;
Timestamp createdAt;
int expiryTime;
boolean isExpired;
public int getTenantId() {
return tenantId;
}
public void setTenantId(int tenantId) {
this.tenantId = tenantId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getOtpToken() {
return otpToken;
}
public void setOtpToken(String otpToken) {
this.otpToken = otpToken;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmailType() {
return emailType;
}
public void setEmailType(String emailType) {
this.emailType = emailType;
}
public String getMetaInfo() { return metaInfo; }
public void setMetaInfo(String metaInfo) {
this.metaInfo = metaInfo;
}
public Timestamp getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Timestamp createdAt) {
this.createdAt = createdAt;
}
public int getExpiryTime() {
return expiryTime;
}
public void setExpiryTime(int expiryTime) {
this.expiryTime = expiryTime;
}
public boolean isExpired() {
return isExpired;
}
public void setExpired(boolean expired) {
isExpired = expired;
}
}

@ -0,0 +1,53 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.otp.mgt.wrapper;
import org.wso2.carbon.device.mgt.common.metadata.mgt.Metadata;
import java.util.List;
public class OTPWrapper {
private String email;
private String emailType;
private String username;
private List<Metadata> properties;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmailType() {
return emailType;
}
public void setEmailType(String emailType) {
this.emailType = emailType;
}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public List<Metadata> getProperties() { return properties; }
public void setProperties(List<Metadata> properties) { this.properties = properties; }
}

@ -44,6 +44,7 @@ public class Item {
private Text text;
private InputList inputList;
private String nullableValue;
private String divider;
@XmlElement(name = "Label")
public String getLabel() {
@ -197,4 +198,13 @@ public class Item {
public void setNullableValue(String nullableValue) {
this.nullableValue = nullableValue;
}
@XmlElement(name = "Divider")
public String getDivider() {
return divider;
}
public void setDivider(String divider) {
this.divider = divider;
}
}

@ -0,0 +1,49 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.roles.config;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "DefaultRoles")
public class DefaultRoles {
private boolean enabled;
private List<Role> roles;
@XmlElement(name = "Enabled", required = true)
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@XmlElementWrapper(name = "Roles", required = true)
@XmlElement(name = "Role", required = true)
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}

@ -0,0 +1,49 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.roles.config;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "Role")
public class Role {
private String name;
private List<String> permissions;
@XmlElement(name = "Name", required = true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElementWrapper(name = "Permissions", required = true)
@XmlElement(name = "Permission", required = true)
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
}

@ -38,6 +38,7 @@ import org.wso2.carbon.device.mgt.common.*;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.general.GeneralConfig;
import org.wso2.carbon.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitationDetails;
import org.wso2.carbon.device.mgt.common.policy.mgt.PolicyMonitoringManager;
import org.wso2.carbon.device.mgt.common.pull.notification.PullNotificationSubscriber;
import org.wso2.carbon.device.mgt.common.push.notification.PushNotificationConfig;
@ -77,4 +78,6 @@ public interface DeviceManagementService {
DeviceTypePlatformDetails getDeviceTypePlatformDetails();
DeviceEnrollmentInvitationDetails getDeviceEnrollmentInvitationDetails();
}

@ -0,0 +1,65 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.common.spi;
import org.wso2.carbon.device.mgt.common.exceptions.BadRequestException;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.OTPManagementException;
import org.wso2.carbon.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OneTimePinDTO;
import org.wso2.carbon.device.mgt.common.otp.mgt.wrapper.OTPWrapper;
import java.util.Map;
public interface OTPManagementService {
/**
* Create OTP token and store tenant details in the DB
* @param otpWrapper OTP Mail Wrapper object which contains tenant details of registering user
* @throws OTPManagementException if error occurs while creating OTP token and storing tenant details.
* @throws BadRequestException if found and incompatible payload to create OTP token.
*/
void sendUserVerifyingMail(OTPWrapper otpWrapper) throws OTPManagementException, DeviceManagementException;
/**
* Check the validity of the OTP
* @param oneTimeToken OTP
* @return The OTP data
* @throws OTPManagementException if error occurred whle verifying validity of the OPT
* @throws BadRequestException if found an null value for OTP
*/
OneTimePinDTO isValidOTP(String oneTimeToken) throws OTPManagementException, BadRequestException;
/**
* Invalidate the OTP and send welcome mail
* @param oneTimeToken OTP
* @param email email address
* @param properties email properties to add to email body
* @throws OTPManagementException if error occurred while invalidate the OTP or send welcome email
*/
void completeSelfRegistration(String oneTimeToken, String email, Map<String, String> properties)
throws OTPManagementException;
/**
* Create OTP token and send device enrollment invitation
* @param deviceEnrollmentInvitation object which contains device enrollment invitation related details
* @throws OTPManagementException if error occurred while creating OTP token &/ sending mail
*/
void sendDeviceEnrollmentInvitationMail(DeviceEnrollmentInvitation deviceEnrollmentInvitation)
throws OTPManagementException;
}

@ -360,6 +360,10 @@
<groupId>org.wso2.carbon.multitenancy</groupId>
<artifactId>org.wso2.carbon.tenant.mgt</artifactId>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
</dependency>
</dependencies>
</project>

@ -116,6 +116,8 @@ public final class DeviceManagementConstants {
public static final String USER_REGISTRATION_TEMPLATE = "user-registration";
public static final String USER_ENROLLMENT_TEMPLATE = "user-enrollment";
public static final String USER_VERIFY_TEMPLATE = "user-verify";
public static final String USER_WELCOME_TEMPLATE = "user-welcome";
public static final String DEFAULT_ENROLLMENT_TEMPLATE = "default-enrollment-invitation";
}
@ -142,6 +144,9 @@ public final class DeviceManagementConstants {
public static final String DEFAULT_DEVICE_USER = "Internal/devicemgt-user";
public static final String DEFAULT_DEVICE_ADMIN = "Internal/devicemgt-admin";
public static final String CLAIM_EMAIL_ADDRESS = "http://wso2.org/claims/emailaddress";
public static final String CLAIM_FIRST_NAME = "http://wso2.org/claims/givenname";
// Permissions that are given for a normal device user.
public static final Permission[] PERMISSIONS_FOR_DEVICE_USER = {
new Permission("/permission/admin/Login", "ui.execute"),
@ -169,4 +174,13 @@ public final class DeviceManagementConstants {
public static final String DEVICE_INFO_PARAM = "device-info";
public static final String APP_USAGE_ENDPOINT = REPORTING_CONTEXT + "/app-usage";
}
public static final class Payload {
private Payload() {
throw new AssertionError();
}
public static final String DEVICE_INFO_DEVICE_NAME = "DEVICE_NAME";
public static final String DEVICE_INFO_IMEI = "IMEI";
public static final String DEVICE_INFO_IMSI = "IMSI";;
}
}

@ -18,6 +18,7 @@
package org.wso2.carbon.device.mgt.core.config;
import org.wso2.carbon.device.mgt.common.enrollment.notification.EnrollmentNotificationConfiguration;
import org.wso2.carbon.device.mgt.common.roles.config.DefaultRoles;
import org.wso2.carbon.device.mgt.core.config.analytics.OperationAnalyticsConfiguration;
import org.wso2.carbon.device.mgt.core.config.archival.ArchivalConfiguration;
import org.wso2.carbon.device.mgt.core.config.cache.CertificateCacheConfiguration;
@ -60,7 +61,7 @@ public final class DeviceManagementConfig {
private RemoteSessionConfiguration remoteSessionConfiguration;
private ArchivalConfiguration archivalConfiguration;
private EnrollmentNotificationConfiguration enrollmentNotificationConfiguration;
private DefaultRoles defaultRoles;
@XmlElement(name = "ManagementRepository", required = true)
public DeviceManagementConfigRepository getDeviceManagementConfigRepository() {
@ -215,5 +216,10 @@ public final class DeviceManagementConfig {
EnrollmentNotificationConfiguration enrollmentNotificationConfiguration) {
this.enrollmentNotificationConfiguration = enrollmentNotificationConfiguration;
}
@XmlElement(name = "DefaultRoles", required = true)
public DefaultRoles getDefaultRoles() { return defaultRoles; }
public void setDefaultRoles(DefaultRoles defaultRoles) { this.defaultRoles = defaultRoles; }
}

@ -134,6 +134,20 @@ public class DeviceInformationManagerImpl implements DeviceInformationManager {
deviceDetailsDAO.addDeviceProperties(injectableProps, device.getId(),
device.getEnrolmentInfo().getId());
}
if (deviceInfo.getDeviceDetailsMap().containsKey(DeviceManagementConstants
.Payload.DEVICE_INFO_DEVICE_NAME) &&
StringUtils.isNotEmpty(deviceInfo.getDeviceDetailsMap()
.get(DeviceManagementConstants.Payload.DEVICE_INFO_DEVICE_NAME))
&& !device.getName().equals(deviceInfo.getDeviceDetailsMap()
.get(DeviceManagementConstants.Payload.DEVICE_INFO_DEVICE_NAME))) {
String name = deviceInfo.getDeviceDetailsMap()
.get(DeviceManagementConstants.Payload.DEVICE_INFO_DEVICE_NAME);
log.info("Device identifier " + device.getDeviceIdentifier() + ", Device name " +
"changed by user from " + device.getName() + " to " + name);
device.setName(name);
}
deviceDAO.updateDevice(device, CarbonContext.getThreadLocalCarbonContext().getTenantId());
DeviceManagementDAOFactory.commitTransaction();
@ -142,8 +156,10 @@ public class DeviceInformationManagerImpl implements DeviceInformationManager {
Object[] metaData = {device.getDeviceIdentifier(), device.getType()};
Object[] payload = new Object[]{
Calendar.getInstance().getTimeInMillis(),
deviceInfo.getDeviceDetailsMap().get("IMEI"),
deviceInfo.getDeviceDetailsMap().get("IMSI"),
deviceInfo.getDeviceDetailsMap().get(DeviceManagementConstants.Payload
.DEVICE_INFO_IMEI),
deviceInfo.getDeviceDetailsMap().get(DeviceManagementConstants.Payload
.DEVICE_INFO_IMSI),
deviceInfo.getDeviceModel(),
deviceInfo.getVendor(),
deviceInfo.getOsVersion(),

@ -35,6 +35,7 @@ import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService
import org.wso2.carbon.device.mgt.common.report.mgt.ReportManagementService;
import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService;
import org.wso2.carbon.device.mgt.common.spi.DeviceTypeGeneratorService;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagerProviderServiceImpl;
@ -56,6 +57,8 @@ import org.wso2.carbon.device.mgt.core.notification.mgt.NotificationManagementSe
import org.wso2.carbon.device.mgt.core.notification.mgt.dao.NotificationManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.operation.mgt.OperationManagerImpl;
import org.wso2.carbon.device.mgt.core.operation.mgt.dao.OperationManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.OTPManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.otp.mgt.service.OTPManagementServiceImpl;
import org.wso2.carbon.device.mgt.core.permission.mgt.PermissionManagerServiceImpl;
import org.wso2.carbon.device.mgt.core.privacy.PrivacyComplianceProvider;
import org.wso2.carbon.device.mgt.core.privacy.impl.PrivacyComplianceProviderImpl;
@ -72,9 +75,11 @@ import org.wso2.carbon.device.mgt.core.task.DeviceTaskManagerService;
import org.wso2.carbon.device.mgt.core.config.ui.UIConfigurationManager;
import org.wso2.carbon.device.mgt.core.util.DeviceManagementSchemaInitializer;
import org.wso2.carbon.device.mgt.core.util.DeviceManagerUtil;
import org.wso2.carbon.device.mgt.core.util.DeviceMgtTenantMgtListener;
import org.wso2.carbon.email.sender.core.service.EmailSenderService;
import org.wso2.carbon.ndatasource.core.DataSourceService;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.stratos.common.listeners.TenantMgtListener;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.Axis2ConfigurationContextObserver;
import org.wso2.carbon.utils.ConfigurationContextService;
@ -176,6 +181,7 @@ public class DeviceManagementServiceComponent {
NotificationManagementDAOFactory.init(dsConfig);
OperationManagementDAOFactory.init(dsConfig);
MetadataManagementDAOFactory.init(dsConfig);
OTPManagementDAOFactory.init(dsConfig.getJndiLookupDefinition().getJndiName());
/*Initialize the device cache*/
DeviceManagerUtil.initializeDeviceCache();
@ -245,6 +251,9 @@ public class DeviceManagementServiceComponent {
componentContext.getBundleContext().registerService(PrivacyComplianceProvider.class.getName(),
privacyComplianceProvider, null);
componentContext.getBundleContext()
.registerService(TenantMgtListener.class.getName(), new DeviceMgtTenantMgtListener(), null);
if (log.isDebugEnabled()) {
log.debug("Device management core bundle has been successfully initialized");
}
@ -325,7 +334,10 @@ public class DeviceManagementServiceComponent {
MetadataManagementService metadataManagementService = new MetadataManagementServiceImpl();
bundleContext.registerService(MetadataManagementService.class.getName(), metadataManagementService, null);
/* Registering App Management service */
OTPManagementService otpManagementService = new OTPManagementServiceImpl();
bundleContext.registerService(OTPManagementService.class.getName(), otpManagementService, null);
/* Registering App Management service */
try {
AppManagementConfigurationManager.getInstance().initConfig();
AppManagementConfig appConfig =

@ -234,7 +234,6 @@ public class NotificationManagementServiceImpl implements NotificationManagement
}
}
@Override
public List<Notification> getNotificationsByStatus(Notification.Status status)
throws NotificationManagementException {

@ -100,5 +100,4 @@ public interface NotificationDAO {
List<Notification> getNotificationsByStatus(PaginationRequest request, Notification.Status status, int tenantId) throws
NotificationManagementException;
}

@ -24,7 +24,12 @@ import org.wso2.carbon.device.mgt.core.notification.mgt.dao.NotificationDAO;
import org.wso2.carbon.device.mgt.core.notification.mgt.dao.NotificationManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.notification.mgt.dao.util.NotificationDAOUtil;
import java.sql.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -94,7 +99,6 @@ public abstract class AbstractNotificationDAOImpl implements NotificationDAO {
NotificationDAOUtil.cleanupResources(stmt, rs);
}
return notification;
}
@Override

@ -49,7 +49,7 @@ public class GenericNotificationDAOImpl extends AbstractNotificationDAOImpl {
"SELECT n1.NOTIFICATION_ID, n1.DEVICE_ID, n1.OPERATION_ID, n1.STATUS, n1.DESCRIPTION," +
" d.DEVICE_IDENTIFICATION, d.NAME as DEVICE_NAME, t.NAME AS DEVICE_TYPE FROM DM_DEVICE d, DM_DEVICE_TYPE t, (SELECT " +
"NOTIFICATION_ID, DEVICE_ID, OPERATION_ID, STATUS, DESCRIPTION FROM DM_NOTIFICATION WHERE " +
"TENANT_ID = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID AND TENANT_ID = ?";
"TENANT_ID = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID AND TENANT_ID = ? ORDER BY n1.NOTIFICATION_ID DESC";
sql = sql + " LIMIT ?,?";
@ -90,7 +90,7 @@ public class GenericNotificationDAOImpl extends AbstractNotificationDAOImpl {
"DM_DEVICE d, DM_DEVICE_TYPE t, (SELECT NOTIFICATION_ID, DEVICE_ID, " +
"OPERATION_ID, STATUS, DESCRIPTION FROM DM_NOTIFICATION WHERE " +
"TENANT_ID = ? AND STATUS = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID " +
"AND TENANT_ID = ?";
"AND TENANT_ID = ? ORDER BY n1.NOTIFICATION_ID DESC";
sql = sql + " LIMIT ?,?";

@ -24,7 +24,11 @@ import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagement
import org.wso2.carbon.device.mgt.core.notification.mgt.dao.NotificationManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.notification.mgt.dao.util.NotificationDAOUtil;
import java.sql.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -33,6 +37,7 @@ import java.util.List;
* This class holds the Oracle implementation of NotificationDAO which can be used to support Oracle db syntax.
*/
public class OracleNotificationDAOImpl extends AbstractNotificationDAOImpl {
@Override
public int addNotification(int deviceId, int tenantId, Notification notification) throws
NotificationManagementException {
@ -81,7 +86,7 @@ public class OracleNotificationDAOImpl extends AbstractNotificationDAOImpl {
"NOTIFICATION_ID, DEVICE_ID, OPERATION_ID, STATUS, DESCRIPTION FROM DM_NOTIFICATION WHERE " +
"TENANT_ID = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID AND TENANT_ID = ?";
sql = sql + " ORDER BY n1.NOTIFICATION_ID OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
sql = sql + " ORDER BY n1.NOTIFICATION_ID DESC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, tenantId);
@ -122,7 +127,7 @@ public class OracleNotificationDAOImpl extends AbstractNotificationDAOImpl {
+ "TENANT_ID = ? AND STATUS = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID "
+ "AND TENANT_ID = ?";
sql = sql + " ORDER BY n1.NOTIFICATION_ID OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
sql = sql + " ORDER BY n1.NOTIFICATION_ID DESC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, tenantId);
@ -149,4 +154,4 @@ public class OracleNotificationDAOImpl extends AbstractNotificationDAOImpl {
}
return notifications;
}
}
}

@ -49,7 +49,7 @@ public class PostgreSQLNotificationDAOImpl extends AbstractNotificationDAOImpl {
"SELECT n1.NOTIFICATION_ID, n1.DEVICE_ID, n1.OPERATION_ID, n1.STATUS, n1.DESCRIPTION," +
" d.DEVICE_IDENTIFICATION, d.NAME as DEVICE_NAME, t.NAME AS DEVICE_TYPE FROM DM_DEVICE d, DM_DEVICE_TYPE t, (SELECT " +
"NOTIFICATION_ID, DEVICE_ID, OPERATION_ID, STATUS, DESCRIPTION FROM DM_NOTIFICATION WHERE " +
"TENANT_ID = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID AND TENANT_ID = ?";
"TENANT_ID = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID AND TENANT_ID = ? ORDER BY n1.NOTIFICATION_ID DESC";
sql = sql + " LIMIT ? OFFSET ?";
@ -90,7 +90,7 @@ public class PostgreSQLNotificationDAOImpl extends AbstractNotificationDAOImpl {
"DM_DEVICE d, DM_DEVICE_TYPE t, (SELECT NOTIFICATION_ID, DEVICE_ID, " +
"OPERATION_ID, STATUS, DESCRIPTION FROM DM_NOTIFICATION WHERE " +
"TENANT_ID = ? AND STATUS = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID " +
"AND TENANT_ID = ?";
"AND TENANT_ID = ? ORDER BY n1.NOTIFICATION_ID DESC";
sql = sql + " LIMIT ? OFFSET ?";

@ -51,7 +51,7 @@ public class SQLServerNotificationDAOImpl extends AbstractNotificationDAOImpl {
"NOTIFICATION_ID, DEVICE_ID, OPERATION_ID, STATUS, DESCRIPTION FROM DM_NOTIFICATION WHERE " +
"TENANT_ID = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID AND TENANT_ID = ?";
sql = sql + " ORDER BY n1.NOTIFICATION_ID OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
sql = sql + " ORDER BY n1.NOTIFICATION_ID DESC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, tenantId);
@ -92,7 +92,7 @@ public class SQLServerNotificationDAOImpl extends AbstractNotificationDAOImpl {
"TENANT_ID = ? AND STATUS = ?) n1 WHERE n1.DEVICE_ID = d.ID AND d.DEVICE_TYPE_ID=t.ID " +
"AND TENANT_ID = ?";
sql = sql + " ORDER BY n1.NOTIFICATION_ID OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
sql = sql + " ORDER BY n1.NOTIFICATION_ID DESC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, tenantId);

@ -0,0 +1,33 @@
/*
* Copyright (c) 2019, 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.
*/
package org.wso2.carbon.device.mgt.core.otp.mgt.dao;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.core.otp.mgt.util.ConnectionManagerUtil;
import java.sql.Connection;
/**
* This class deals with getting the DB connection.
*/
public abstract class AbstractDAOImpl {
protected Connection getDBConnection() throws DBConnectionException {
return ConnectionManagerUtil.getDBConnection();
}
}

@ -0,0 +1,64 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.core.otp.mgt.dao;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OneTimePinDTO;
import org.wso2.carbon.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
public interface OTPManagementDAO {
/**
* Save OTP token data and tenant details of registering user
* @param oneTimePinDTO OTPMailDTO
* @return Primary key of the newly adding data raw
* @throws OTPManagementDAOException if error occurred whule storing data
*/
int addOTPData(OneTimePinDTO oneTimePinDTO) throws OTPManagementDAOException;
/**
* Get OTP data for requesting One Time Token
* @param oneTimeToken One Time Token
* @return {@link OneTimePinDTO}
* @throws OTPManagementDAOException if error ocured while getting OTP data for requesting one time token
*/
OneTimePinDTO getOTPDataByToken (String oneTimeToken) throws OTPManagementDAOException;
/**
* Expire the OTP
* @param oneTimeToken OTP
* @throws OTPManagementDAOException if error occurred while updating the OTP validity.
*/
boolean expireOneTimeToken(String oneTimeToken) throws OTPManagementDAOException;
/**
* Update OTP with renewed OTP
* @param id ID
* @param oneTimeToken One Time Token
* @throws OTPManagementDAOException if error occured while updating OTP
*/
void renewOneTimeToken(int id, String oneTimeToken) throws OTPManagementDAOException;
/**
* To veify whether email and email type exists or not
* @param email email
* @param emailType email type
* @return true if email and email type exists otherwise returns false
* @throws OTPManagementDAOException if error occurred while verify existance of the email and email type
*/
boolean isEmailExist (String email, String emailType) throws OTPManagementDAOException;
}

@ -0,0 +1,76 @@
/*
* 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.
*/
package org.wso2.carbon.device.mgt.core.otp.mgt.dao;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.common.exceptions.UnsupportedDatabaseEngineException;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.GenericOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.OracleOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.PostgreSQLOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.SQLServerOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.util.ConnectionManagerUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* This class intends to act as the primary entity that hides all DAO instantiation related complexities and logic so
* that the business objection handling layer doesn't need to be aware of the same providing seamless plug-ability of
* different data sources, connection acquisition mechanisms as well as different forms of DAO implementations to the
* high-level implementations that require Application management related metadata persistence.
*/
public class OTPManagementDAOFactory {
private static String databaseEngine;
private static final Log log = LogFactory.getLog(OTPManagementDAOFactory.class);
public static void init(String datasourceName) {
ConnectionManagerUtil.resolveDataSource(datasourceName);
databaseEngine = ConnectionManagerUtil.getDatabaseType();
}
public static void init(DataSource dtSource) {
try (Connection connection = dtSource.getConnection()) {
databaseEngine = connection.getMetaData().getDatabaseProductName();
} catch (SQLException e) {
log.error("Error occurred while retrieving config.datasource connection", e);
}
}
public static OTPManagementDAO getOTPManagementDAO() {
if (databaseEngine != null) {
switch (databaseEngine) {
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_H2:
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_MYSQL:
return new GenericOTPManagementDAOImpl();
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_POSTGRESQL:
return new PostgreSQLOTPManagementDAOImpl();
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_MSSQL:
return new SQLServerOTPManagementDAOImpl();
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_ORACLE:
return new OracleOTPManagementDAOImpl();
default:
throw new UnsupportedDatabaseEngineException("Unsupported database engine : " + databaseEngine);
}
}
throw new IllegalStateException("Database engine has not initialized properly.");
}
}

@ -0,0 +1,243 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.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.
*/
package org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OneTimePinDTO;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.AbstractDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.OTPManagementDAO;
import org.wso2.carbon.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar;
public class GenericOTPManagementDAOImpl extends AbstractDAOImpl implements OTPManagementDAO {
private static final Log log = LogFactory.getLog(GenericOTPManagementDAOImpl.class);
@Override
public int addOTPData(OneTimePinDTO oneTimePinDTO) throws OTPManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to create an OTP data entry");
log.debug("OTP Details : ");
log.debug("OTP key : " + oneTimePinDTO.getOtpToken() + " Email : " + oneTimePinDTO.getEmail());
}
String sql = "INSERT INTO DM_OTP_DATA "
+ "(OTP_TOKEN, "
+ "EMAIL, "
+ "EMAIL_TYPE, "
+ "META_INFO, "
+ "CREATED_AT,"
+ "TENANT_ID,"
+ "USERNAME) VALUES (?, ?, ?, ?, ?, ?, ?)";
try {
Connection conn = this.getDBConnection();
Calendar calendar = Calendar.getInstance();
Timestamp timestamp = new Timestamp(calendar.getTime().getTime());
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, oneTimePinDTO.getOtpToken());
stmt.setString(2, oneTimePinDTO.getEmail());
stmt.setString(3, oneTimePinDTO.getEmailType());
stmt.setString(4, oneTimePinDTO.getMetaInfo());
stmt.setTimestamp(5, timestamp);
stmt.setInt(6, oneTimePinDTO.getTenantId());
stmt.setString(7, oneTimePinDTO.getUsername());
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
return rs.getInt(1);
}
return -1;
}
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to create an opt entry for email "
+ oneTimePinDTO.getEmail();
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while executing SQL to create an otp entry for email " + oneTimePinDTO.getEmail();
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
}
}
@Override
public OneTimePinDTO getOTPDataByToken (String oneTimeToken) throws OTPManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to get an OTP data entry for OTP");
log.debug("OTP Details : OTP key : " + oneTimeToken );
}
String sql = "SELECT "
+ "ID, "
+ "OTP_TOKEN, "
+ "EMAIL, "
+ "EMAIL_TYPE, "
+ "META_INFO, "
+ "CREATED_AT, "
+ "EXPIRY_TIME, "
+ "IS_EXPIRED, "
+ "TENANT_ID, "
+ "USERNAME FROM DM_OTP_DATA "
+ "WHERE OTP_TOKEN = ?";
try {
Connection conn = this.getDBConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, oneTimeToken);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
OneTimePinDTO oneTimePinDTO = new OneTimePinDTO();
oneTimePinDTO.setId(rs.getInt("ID"));
oneTimePinDTO.setOtpToken(rs.getString("OTP_TOKEN"));
oneTimePinDTO.setEmail(rs.getString("EMAIL"));
oneTimePinDTO.setEmailType(rs.getString("EMAIL_TYPE"));
oneTimePinDTO.setMetaInfo(rs.getString("META_INFO"));
oneTimePinDTO.setCreatedAt(rs.getTimestamp("CREATED_AT"));
oneTimePinDTO.setExpiryTime(rs.getInt("EXPIRY_TIME"));
oneTimePinDTO.setExpired(rs.getBoolean("IS_EXPIRED"));
oneTimePinDTO.setTenantId(rs.getInt("TENANT_ID"));
oneTimePinDTO.setUsername(rs.getString("USERNAME"));
return oneTimePinDTO;
}
return null;
}
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to get OPT data for given OTP. OTP: "
+ oneTimeToken;
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while executing SQL to get OTP data for OTP. One time token: " + oneTimeToken;
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
}
}
@Override
public boolean expireOneTimeToken(String oneTimeToken) throws OTPManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to update an OTP data entry for OTP");
log.debug("OTP Details : OTP key : " + oneTimeToken );
}
String sql = "UPDATE DM_OTP_DATA "
+ "SET "
+ "IS_EXPIRED = ? "
+ "WHERE OTP_TOKEN = ?";
try {
Connection conn = this.getDBConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setBoolean(1, true);
stmt.setString(2, oneTimeToken);
return stmt.executeUpdate() == 1;
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to update the OTP token validity.";
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred when obtaining database connection for updating the OTP token validity.";
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
}
}
@Override
public void renewOneTimeToken(int id, String oneTimeToken) throws OTPManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to update an OTP data entry for OTP");
log.debug("OTP Details : OTP key : " + oneTimeToken );
}
String sql = "UPDATE DM_OTP_DATA "
+ "SET "
+ "OTP_TOKEN = ?, "
+ "CREATED_AT = ? "
+ "WHERE ID = ?";
try {
Connection conn = this.getDBConnection();
Calendar calendar = Calendar.getInstance();
Timestamp timestamp = new Timestamp(calendar.getTime().getTime());
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, oneTimeToken);
stmt.setTimestamp(2, timestamp);
stmt.setInt(3, id);
stmt.executeUpdate();
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to update the OTP token.";
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred when executing sql query to update the OTP token.";
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
}
}
@Override
public boolean isEmailExist (String email, String emailType) throws OTPManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to verify whether email was registed with emai type in OTP");
log.debug("OTP Details : email : " + email + " email type: " + emailType );
}
String sql = "SELECT "
+ "ID "
+ "FROM DM_OTP_DATA "
+ "WHERE EMAIL = ? AND "
+ "EMAIL_TYPE = ?";
try {
Connection conn = this.getDBConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, email);
stmt.setString(2, emailType);
try (ResultSet rs = stmt.executeQuery()) {
return rs.next();
}
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to verify email and email type exist in OTP."
+ " Email: " + email + "Email Type: " + emailType;
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while executing SQL to verify email and email type exist in OTP. Email: "
+ email + "Email Type: " + emailType;
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save