diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js index 4f0862d4276..8d3539854a4 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js @@ -40,7 +40,7 @@ import Policies from './scenes/Home/scenes/Policies'; import AddNewPolicy from './scenes/Home/scenes/Policies/scenes/AddNewPolicy'; import Roles from './scenes/Home/scenes/Roles'; import DeviceTypes from './scenes/Home/scenes/DeviceTypes'; -import Certificates from './scenes/Home/scenes/Certificates'; +import Certificates from './scenes/Home/scenes/Configurations/scenes/Certificates'; import Devices from './scenes/Home/scenes/Devices'; const routes = [ @@ -105,7 +105,7 @@ const routes = [ exact: true, }, { - path: '/entgra/certificates', + path: '/entgra/configurations/certificates', component: Certificates, exact: true, }, diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js index 6a8fc986987..ab941cfb232 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/index.js @@ -151,7 +151,7 @@ class Home extends React.Component { } > - + Certificates diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Certificates/components/CertificateTable/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Configurations/scenes/Certificates/components/CertificateTable/index.js similarity index 98% rename from components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Certificates/components/CertificateTable/index.js rename to components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Configurations/scenes/Certificates/components/CertificateTable/index.js index f59fb81ffa8..cafeaaca992 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Certificates/components/CertificateTable/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Configurations/scenes/Certificates/components/CertificateTable/index.js @@ -30,7 +30,7 @@ import { import TimeAgo from 'javascript-time-ago'; // Load locale-specific relative date/time formatting rules. import en from 'javascript-time-ago/locale/en'; -import { withConfigContext } from '../../../../../../components/ConfigContext'; +import { withConfigContext } from '../../../../../../../../components/ConfigContext'; import Moment from 'react-moment'; const { Paragraph, Text } = Typography; diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Certificates/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Configurations/scenes/Certificates/index.js similarity index 100% rename from components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Certificates/index.js rename to components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Configurations/scenes/Certificates/index.js diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/components/ExternalDevicesModal/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/components/ExternalDevicesModal/index.js new file mode 100644 index 00000000000..c1c7d1878c1 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/components/ExternalDevicesModal/index.js @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved. + * + * Entgra (pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { Button, Form, Input, Modal, notification, Col, Row } from 'antd'; +import axios from 'axios'; +import { withConfigContext } from '../../../../../../../../components/ConfigContext'; +import { handleApiError } from '../../../../../../../../services/utils/errorHandler'; +const InputGroup = Input.Group; + +class ExternalDevicesModal extends React.Component { + constructor(props) { + super(props); + this.config = this.props.context; + this.state = { + isDeviceEditModalVisible: false, + metaData: [], + }; + } + + openDeviceEditModal = () => { + this.setState({ + isDeviceEditModalVisible: true, + }); + this.getExternalDevicesForUser(this.props.user); + }; + + onCancelHandler = () => { + this.setState({ + isDeviceEditModalVisible: false, + }); + }; + + getExternalDevicesForUser = userName => { + let apiURL = + window.location.origin + + this.config.serverConfig.invoker.uri + + this.config.serverConfig.invoker.deviceMgt + + `/users/claims/${userName}`; + + axios + .get(apiURL) + .then(res => { + if (res.status === 200) { + if (res.data.data.hasOwnProperty('http://wso2.org/claims/devices')) { + this.setState({ + metaData: JSON.parse( + res.data.data['http://wso2.org/claims/devices'], + ), + }); + } + } + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to retrieve claims.', + ); + }); + }; + + setExternalDevicesForUser = (userName, payload) => { + let apiURL = + window.location.origin + + this.config.serverConfig.invoker.uri + + this.config.serverConfig.invoker.deviceMgt + + `/users/claims/${userName}`; + + axios + .put(apiURL, payload) + .then(res => { + if (res.status === 200) { + notification.success({ + message: 'Done', + duration: 0, + description: 'Succussfully updated.', + }); + } + this.setState({ + isDeviceEditModalVisible: false, + }); + }) + .catch(error => { + handleApiError(error, 'Error occurred while trying to update claims.'); + }); + }; + + onSubmitClaims = e => { + this.props.form.validateFields(['meta'], (err, values) => { + if (!err) { + this.setExternalDevicesForUser(this.props.user, this.state.metaData); + } + }); + }; + + addNewMetaData = () => { + this.setState({ + metaData: [...this.state.metaData, { deviceName: '', id: '' }], + }); + }; + + render() { + const { getFieldDecorator } = this.props.form; + const { metaData } = this.state; + return ( +
+
+ +
+
+ + Cancel + , + , + ]} + > +
+

Add or edit external device information

+
+ + {getFieldDecorator('meta', {})( +
+ {metaData.map((data, index) => { + return ( + + + + { + metaData[index].deviceName = + e.currentTarget.value; + this.setState({ + metaData, + }); + }} + /> + + + { + metaData[index].id = e.currentTarget.value; + this.setState({ + metaData, + }); + }} + /> + + + +
, + )} +
+
+
+
+
+
+ ); + } +} + +export default withConfigContext( + Form.create({ name: 'external-device-modal' })(ExternalDevicesModal), +); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/index.js index c97f5897b82..c2296c3d6fe 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/scenes/Home/scenes/Users/components/UsersTable/index.js @@ -27,6 +27,7 @@ import UsersDevices from './components/UserDevices'; import AddUser from './components/AddUser'; import UserActions from './components/UserActions'; import Filter from '../../../../components/Filter'; +import ExternalDevicesModal from './components/ExternalDevicesModal'; const ButtonGroup = Button.Group; let apiUrl; @@ -114,8 +115,6 @@ class UsersTable extends React.Component { data: res.data.data.users, pagination, }); - - console.log(res.data.data); } }) .catch(error => { @@ -224,6 +223,7 @@ class UsersTable extends React.Component { title: 'First Name', dataIndex: 'firstname', key: 'firstname', + width: 200, }, { title: 'Last Name', @@ -260,6 +260,12 @@ class UsersTable extends React.Component { ), }, + { + title: 'External Device Claims', + dataIndex: 'claims', + key: 'claims', + render: (id, row) => , + }, { title: 'Action', dataIndex: 'id', diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/UserManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/UserManagementService.java index d50f4cb50b3..9faf6d7ac2e 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/UserManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/UserManagementService.java @@ -34,6 +34,8 @@ */ 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; @@ -1009,4 +1011,161 @@ public interface UserManagementService { "Provide the value in the following format: EEE, d MMM yyyy HH:mm:ss Z\n." + "Example: Mon, 05 Jan 2014 15:10:00 +0200") @HeaderParam("If-Modified-Since") String ifModifiedSince); + + @PUT + @Path("/claims/{username}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "PUT", + value = "Updating external device claims of user", + notes = "Update external device claims of a user registered with Entgra IoTS using the REST API.", + response = BasicUserInfo.class, + tags = "User Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:users:details") + }) + } + ) + @ApiResponses(value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully updated external device claims of user.", + response = BasicUserInfo.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 = 404, + message = "Not Found. \n The specified resource does not exist.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server ErrorResponse. \n Server error occurred while" + + " fetching the user details.", + response = ErrorResponse.class) + }) + Response updateUserClaimsForDevices( + @ApiParam( + name = "username", + value = "Provide the username of the user.", + required = true, + defaultValue = "admin") + @PathParam("username") String username, + @ApiParam( + name = "device list", + value = "Array of objects with device details", + required = true) JsonArray deviceList); + + @GET + @Path("/claims/{username}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting external device claims of user", + notes = "Get external device claims of a user registered with Entgra IoTS using the REST API.", + response = BasicUserInfo.class, + tags = "User Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:users:details") + }) + } + ) + @ApiResponses(value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully fetched external device claims of user.", + response = BasicUserInfo.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 = 404, + message = "Not Found. \n The specified resource does not exist.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server ErrorResponse. \n Server error occurred while" + + " fetching the ruser details.", + response = ErrorResponse.class) + }) + Response getUserClaimsForDevices( + @ApiParam( + name = "username", + value = "Provide the username of the user.", + required = true, + defaultValue = "admin") + @PathParam("username") String username); + + @DELETE + @Path("/claims/{username}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "DELETE", + value = "Deleting external device claims of user", + notes = "Delete external device claims of user registered with Entgra IoTS using the REST API.", + response = BasicUserInfo.class, + tags = "User Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:users:details") + }) + } + ) + @ApiResponses(value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully deleted external device claims of user.", + response = BasicUserInfo.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 = 404, + message = "Not Found. \n The specified resource does not exist.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server ErrorResponse. \n Server error occurred while" + + " fetching the ruser details.", + response = ErrorResponse.class) + }) + Response deleteUserClaimsForDevices( + @ApiParam( + name = "username", + value = "Provide the username of the user.", + required = true, + defaultValue = "admin") + @PathParam("username") String username); } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/UserManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/UserManagementServiceImpl.java index 251aa0eb64e..a059ce3c474 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/UserManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/UserManagementServiceImpl.java @@ -33,6 +33,7 @@ */ package org.wso2.carbon.device.mgt.jaxrs.service.impl; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; @@ -71,6 +72,7 @@ import org.wso2.carbon.user.api.RealmConfiguration; import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.utils.CarbonUtils; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; @@ -920,6 +922,113 @@ public class UserManagementServiceImpl implements UserManagementService { } } + @PUT + @Override + @Path("/claims/{username}") + public Response updateUserClaimsForDevices( + @PathParam("username") String username, + JsonArray deviceList) { + try { + RealmConfiguration realmConfiguration = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getUserRealm() + .getRealmConfiguration(); + String domain = realmConfiguration + .getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME); + if (!StringUtils.isBlank(domain)) { + username = domain + Constants.FORWARD_SLASH + username; + } + UserStoreManager userStoreManager = DeviceMgtAPIUtils.getUserStoreManager(); + if (!userStoreManager.isExistingUser(username)) { + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " does not exist."); + } + return Response.status(Response.Status.NOT_FOUND).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage( + "User doesn't exist.").build()).build(); + } + Map userClaims = + this.buildExternalDevicesUserClaims(username, domain, deviceList, userStoreManager); + userStoreManager.setUserClaimValues(username, userClaims, domain); + return Response.status(Response.Status.OK).entity(userClaims).build(); + } catch (UserStoreException e) { + String msg = "Error occurred while updating external device claims of the user '" + username + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + } + + @GET + @Override + @Path("/claims/{username}") + public Response getUserClaimsForDevices( + @PathParam("username") String username) { + try { + RealmConfiguration realmConfiguration = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getUserRealm() + .getRealmConfiguration(); + String domain = realmConfiguration + .getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME); + if (!StringUtils.isBlank(domain)) { + username = domain + Constants.FORWARD_SLASH + username; + } + UserStoreManager userStoreManager = DeviceMgtAPIUtils.getUserStoreManager(); + if (!userStoreManager.isExistingUser(username)) { + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " does not exist."); + } + return Response.status(Response.Status.NOT_FOUND).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage( + "User doesn't exist.").build()).build(); + } + String[] claimArray = {Constants.USER_CLAIM_DEVICES}; + Map claims = userStoreManager.getUserClaimValues(username, claimArray, domain); + return Response.status(Response.Status.OK).entity(claims).build(); + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving external device claims of the user '" + username + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + } + + @DELETE + @Override + @Path("/claims/{username}") + public Response deleteUserClaimsForDevices( + @PathParam("username") String username) { + try { + RealmConfiguration realmConfiguration = PrivilegedCarbonContext.getThreadLocalCarbonContext() + .getUserRealm() + .getRealmConfiguration(); + String domain = realmConfiguration + .getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME); + if (!StringUtils.isBlank(domain)) { + username = domain + Constants.FORWARD_SLASH + username; + } + UserStoreManager userStoreManager = DeviceMgtAPIUtils.getUserStoreManager(); + if (!userStoreManager.isExistingUser(username)) { + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " does not exist."); + } + return Response.status(Response.Status.NOT_FOUND).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage( + "User doesn't exist.").build()).build(); + } + String[] claimArray = {Constants.USER_CLAIM_DEVICES}; + userStoreManager.deleteUserClaimValues( + username, + claimArray, + domain); + return Response.status(Response.Status.OK).entity(claimArray).build(); + } catch (UserStoreException e) { + String msg = "Error occurred while deleting external device claims of the user '" + username + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + } + private Map buildDefaultUserClaims(String firstName, String lastName, String emailAddress, boolean isFresh) { Map defaultUserClaims = new HashMap<>(); @@ -937,6 +1046,40 @@ public class UserManagementServiceImpl implements UserManagementService { return defaultUserClaims; } + /** + * This method is used to build String map for user claims with updated external device details + * + * @param username username of the particular user + * @param domain domain of the particular user + * @param deviceList Array of external device details + * @param userStoreManager {@link UserStoreManager} instance + * @return String map + * @throws UserStoreException If any error occurs while calling into UserStoreManager service + */ + private Map buildExternalDevicesUserClaims( + String username, + String domain, + JsonArray deviceList, + UserStoreManager userStoreManager) throws UserStoreException { + Map userClaims; + String[] claimArray = { + Constants.USER_CLAIM_FIRST_NAME, + Constants.USER_CLAIM_LAST_NAME, + Constants.USER_CLAIM_EMAIL_ADDRESS, + Constants.USER_CLAIM_MODIFIED + }; + userClaims = userStoreManager.getUserClaimValues(username, claimArray, domain); + if (userClaims.containsKey(Constants.USER_CLAIM_DEVICES)) { + userClaims.replace(Constants.USER_CLAIM_DEVICES, deviceList.toString()); + } else { + userClaims.put(Constants.USER_CLAIM_DEVICES, deviceList.toString()); + } + if (log.isDebugEnabled()) { + log.debug("Claim map is created for user: " + username + ", claims:" + userClaims.toString()); + } + return userClaims; + } + private String generateInitialUserPassword() { int passwordLength = 6; //defining the pool of characters to be used for initial password generation diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/util/Constants.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/util/Constants.java index 742dc62037c..75f560ba68a 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/util/Constants.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/util/Constants.java @@ -45,12 +45,14 @@ public class Constants { public static final String USER_CLAIM_LAST_NAME = "http://wso2.org/claims/lastname"; public static final String USER_CLAIM_CREATED = "http://wso2.org/claims/created"; public static final String USER_CLAIM_MODIFIED = "http://wso2.org/claims/modified"; + public static final String USER_CLAIM_DEVICES = "http://wso2.org/claims/devices"; public static final String PRIMARY_USER_STORE = "PRIMARY"; public static final String DEFAULT_STREAM_VERSION = "1.0.0"; public static final String SCOPE = "scope"; public static final String JDBC_USERSTOREMANAGER = "org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager"; public static final String DEFAULT_SIMPLE_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss Z"; public static final int DEFAULT_PAGE_LIMIT = 50; + public static final String FORWARD_SLASH = "/"; public final class ErrorMessages {