/* * Copyright 2014-2016 CyberVision, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kaaproject.kaa.server.admin.services; import static org.kaaproject.kaa.server.admin.services.util.Utils.checkFieldUniquieness; import static org.kaaproject.kaa.server.admin.services.util.Utils.checkNotNull; import static org.kaaproject.kaa.server.admin.services.util.Utils.getCurrentUser; import static org.kaaproject.kaa.server.admin.shared.schema.ConverterType.CONFIGURATION_FORM_AVRO_CONVERTER; import static org.kaaproject.kaa.server.admin.shared.util.Utils.isEmpty; import static org.kaaproject.kaa.server.common.dao.service.Validator.validateNotNull; import org.apache.avro.Schema; import org.apache.avro.generic.GenericRecord; import org.kaaproject.avro.ui.converter.CtlSource; import org.kaaproject.avro.ui.converter.FormAvroConverter; import org.kaaproject.avro.ui.converter.SchemaFormAvroConverter; import org.kaaproject.avro.ui.shared.Fqn; import org.kaaproject.avro.ui.shared.RecordField; import org.kaaproject.kaa.common.avro.GenericAvroConverter; import org.kaaproject.kaa.common.dto.AbstractSchemaDto; import org.kaaproject.kaa.common.dto.ApplicationDto; import org.kaaproject.kaa.common.dto.EndpointGroupDto; import org.kaaproject.kaa.common.dto.EndpointProfileDto; import org.kaaproject.kaa.common.dto.KaaAuthorityDto; import org.kaaproject.kaa.common.dto.UserDto; import org.kaaproject.kaa.common.dto.ctl.CTLSchemaDto; import org.kaaproject.kaa.common.dto.plugin.PluginDto; import org.kaaproject.kaa.server.admin.services.cache.CacheService; import org.kaaproject.kaa.server.admin.services.dao.PropertiesFacade; import org.kaaproject.kaa.server.admin.services.dao.UserFacade; import org.kaaproject.kaa.server.admin.services.entity.AuthUserDto; import org.kaaproject.kaa.server.admin.services.entity.CreateUserResult; import org.kaaproject.kaa.server.admin.services.entity.User; import org.kaaproject.kaa.server.admin.services.messaging.MessagingService; import org.kaaproject.kaa.server.admin.services.schema.ConfigurationSchemaFormAvroConverter; import org.kaaproject.kaa.server.admin.services.schema.CtlSchemaParser; import org.kaaproject.kaa.server.admin.services.schema.EcfSchemaFormAvroConverter; import org.kaaproject.kaa.server.admin.services.schema.SimpleSchemaFormAvroConverter; import org.kaaproject.kaa.server.admin.services.util.Utils; import org.kaaproject.kaa.server.admin.shared.plugin.PluginInfoDto; import org.kaaproject.kaa.server.admin.shared.schema.ConverterType; import org.kaaproject.kaa.server.admin.shared.schema.CtlSchemaFormDto; import org.kaaproject.kaa.server.admin.shared.services.KaaAdminServiceException; import org.kaaproject.kaa.server.admin.shared.services.ServiceErrorCode; import org.kaaproject.kaa.server.common.core.schema.KaaSchemaFactoryImpl; import org.kaaproject.kaa.server.common.dao.EndpointService; import org.kaaproject.kaa.server.common.plugin.KaaPluginConfig; import org.kaaproject.kaa.server.common.plugin.PluginConfig; import org.kaaproject.kaa.server.common.plugin.PluginType; import org.kaaproject.kaa.server.control.service.ControlService; import org.kaaproject.kaa.server.control.service.exception.ControlServiceException; import org.kaaproject.kaa.server.control.service.sdk.SchemaUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.mail.MailException; import org.springframework.security.crypto.password.PasswordEncoder; import java.io.IOException; import java.security.InvalidParameterException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public abstract class AbstractAdminService implements InitializingBean { /** * The Constant LOG. */ private static final Logger LOG = LoggerFactory.getLogger(AbstractAdminService.class); @Autowired ControlService controlService; @Autowired UserFacade userFacade; @Autowired PropertiesFacade propertiesFacade; @Autowired MessagingService messagingService; @Autowired EndpointService endpointService; @Autowired CacheService cacheService; @Value("#{properties[additional_plugins_scan_package]}") String additionalPluginsScanPackage; @Autowired PasswordEncoder passwordEncoder; SchemaFormAvroConverter simpleSchemaFormAvroConverter; SchemaFormAvroConverter commonSchemaFormAvroConverter; SchemaFormAvroConverter configurationSchemaFormAvroConverter; SchemaFormAvroConverter ecfSchemaFormAvroConverter; Map<PluginType, Map<String, PluginInfoDto>> pluginsInfo = new HashMap<>(); { for (PluginType type : PluginType.values()) { pluginsInfo.put(type, new HashMap<String, PluginInfoDto>()); } } public void setPasswordEncoder(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } void validateRecordSchema(Schema schema) throws KaaAdminServiceException { SchemaUtil.compileAvroSchema(schema); if (schema.getType() != Schema.Type.RECORD) { throw new KaaAdminServiceException("Schema " + schema.getFullName() + " is not a record schema!", ServiceErrorCode.INVALID_SCHEMA); } } void validateRecordSchema(String avroSchema, boolean isCtl) throws KaaAdminServiceException { Schema schema = validateSchema(avroSchema, isCtl); validateRecordSchema(schema); } byte[] getFileContent(String fileItemName) throws KaaAdminServiceException { if (!isEmpty(fileItemName)) { try { byte[] data = cacheService.uploadedFile(fileItemName, null); if (data == null) { throw new KaaAdminServiceException( "Unable to get file content!", ServiceErrorCode.FILE_NOT_FOUND); } return data; } finally { cacheService.removeUploadedFile(fileItemName); } } else { throw new KaaAdminServiceException( "Unable to get file content, file item name is empty!", ServiceErrorCode.FILE_NOT_FOUND); } } void checkAuthority(KaaAuthorityDto... authorities) throws KaaAdminServiceException { AuthUserDto authUser = getCurrentUser(); boolean matched = false; for (KaaAuthorityDto authority : authorities) { if (authUser.getAuthority() == authority) { matched = true; break; } } if (!matched) { throw new KaaAdminServiceException( "You do not have permission to perform this operation!", ServiceErrorCode.PERMISSION_DENIED); } } void checkUserId(String userId) throws KaaAdminServiceException { AuthUserDto authUser = getCurrentUser(); if (authUser.getId() == null || !authUser.getId().equals(userId)) { throw new KaaAdminServiceException(ServiceErrorCode.PERMISSION_DENIED); } } void checkTenantId(String tenantId) throws KaaAdminServiceException { AuthUserDto authUser = getCurrentUser(); if (authUser.getTenantId() == null || !authUser.getTenantId().equals(tenantId)) { throw new KaaAdminServiceException(ServiceErrorCode.PERMISSION_DENIED); } } ApplicationDto checkApplicationId(String applicationId) throws KaaAdminServiceException { try { if (isEmpty(applicationId)) { throw new IllegalArgumentException("The applicationId parameter is empty."); } ApplicationDto application = controlService.getApplication(applicationId); checkApplication(application); return application; } catch (Exception ex) { throw Utils.handleException(ex); } } String checkApplicationToken(String applicationToken) throws KaaAdminServiceException { try { if (isEmpty(applicationToken)) { throw new KaaAdminServiceException(ServiceErrorCode.INVALID_ARGUMENTS); } ApplicationDto application = controlService.getApplicationByApplicationToken( applicationToken); checkApplication(application); return application.getId(); } catch (Exception ex) { throw Utils.handleException(ex); } } void checkApplication(ApplicationDto application) throws KaaAdminServiceException { checkNotNull(application); checkTenantId(application.getTenantId()); } EndpointGroupDto checkEndpointGroupId(String endpointGroupId) throws KaaAdminServiceException { try { if (isEmpty(endpointGroupId)) { throw new IllegalArgumentException("The endpointGroupId parameter is empty."); } EndpointGroupDto endpointGroup = controlService.getEndpointGroup(endpointGroupId); checkNotNull(endpointGroup); checkApplicationId(endpointGroup.getApplicationId()); return endpointGroup; } catch (Exception ex) { throw Utils.handleException(ex); } } EndpointProfileDto checkEndpointProfile(byte[] endpointKeyHash) throws KaaAdminServiceException { try { validateNotNull(endpointKeyHash, "Missing endpoint key hash"); EndpointProfileDto endpointProfile = endpointService.findEndpointProfileByKeyHash(endpointKeyHash); checkNotNull(endpointProfile); checkApplicationId(endpointProfile.getApplicationId()); return endpointProfile; } catch (Exception ex) { throw Utils.handleException(ex); } } String getTenantId() throws KaaAdminServiceException { return getCurrentUser().getTenantId(); } boolean isGroupAll(EndpointGroupDto groupDto) { boolean result = false; if (groupDto != null && groupDto.getWeight() == 0) { result = true; } return result; } @Override public void afterPropertiesSet() throws Exception { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(KaaPluginConfig.class)); scanPluginsPackage(scanner, "org.kaaproject.kaa.server.appenders"); scanPluginsPackage(scanner, "org.kaaproject.kaa.server.verifiers"); if (!isEmpty(additionalPluginsScanPackage)) { scanPluginsPackage(scanner, additionalPluginsScanPackage); } simpleSchemaFormAvroConverter = new SimpleSchemaFormAvroConverter(); commonSchemaFormAvroConverter = new SchemaFormAvroConverter(); configurationSchemaFormAvroConverter = new ConfigurationSchemaFormAvroConverter(); ecfSchemaFormAvroConverter = new EcfSchemaFormAvroConverter(); } private void scanPluginsPackage(ClassPathScanningCandidateComponentProvider scanner, String packageName) throws Exception { Set<BeanDefinition> beans = scanner.findCandidateComponents(packageName); for (BeanDefinition bean : beans) { Class<?> clazz = Class.forName(bean.getBeanClassName()); KaaPluginConfig annotation = clazz.getAnnotation(KaaPluginConfig.class); PluginConfig pluginConfig = (PluginConfig) clazz.newInstance(); RecordField fieldConfiguration = FormAvroConverter.createRecordFieldFromSchema(pluginConfig.getPluginConfigSchema()); PluginInfoDto pluginInfo = new PluginInfoDto(pluginConfig.getPluginTypeName(), fieldConfiguration, pluginConfig.getPluginClassName()); pluginsInfo.get(annotation.pluginType()).put(pluginInfo.getPluginClassName(), pluginInfo); } } void setSchema(AbstractSchemaDto schemaDto, byte[] data) throws KaaAdminServiceException { String schema = new String(data); validateRecordSchema(schema, false); schemaDto.setSchema(new KaaSchemaFactoryImpl().createDataSchema(schema).getRawSchema()); } Schema validateSchema(String avroSchema, boolean isCtl) throws KaaAdminServiceException { try { if (isCtl) { return CtlSchemaParser.parseStringCtlSchema(avroSchema); } else { Schema.Parser parser = new Schema.Parser(); return parser.parse(avroSchema); } } catch (Exception spe) { LOG.error("Exception catched: ", spe); throw new KaaAdminServiceException(spe.getMessage(), ServiceErrorCode.INVALID_SCHEMA); } } RecordField createRecordFieldFromCtlSchemaAndBody(String ctlSchemaId, String body) throws KaaAdminServiceException { try { RecordField recordField; CTLSchemaDto ctlSchema = controlService.getCtlSchemaById(ctlSchemaId); Schema schema = controlService.exportCtlSchemaFlatAsSchema(ctlSchema); if (!isEmpty(body)) { GenericAvroConverter<GenericRecord> converter = new GenericAvroConverter<>(schema); GenericRecord record = converter.decodeJson(body); recordField = FormAvroConverter.createRecordFieldFromGenericRecord(record); } else { recordField = FormAvroConverter.createRecordFieldFromSchema(schema); } return recordField; } catch (Exception ex) { throw Utils.handleException(ex); } } CreateUserResult saveUser(org.kaaproject.kaa.common.dto.admin.UserDto user, boolean doSendTempPassword) throws Exception { CreateUserResult result = userFacade.saveUserDto(user, passwordEncoder); try { if (!isEmpty(result.getPassword()) && doSendTempPassword) { messagingService.sendTempPassword(user.getUsername(), result.getPassword(), user.getMail()); } } catch (Exception ex) { LOG.error("Can't send temporary password. Exception was catched: ", ex); if (isEmpty(user.getExternalUid())) { userFacade.deleteUser(result.getUserId()); } StringBuilder errorMessage = new StringBuilder("Failed to send email" + " with temporary password. "); if (ex instanceof MailException) { errorMessage.append("Please, check outgoing email settings. "); } throw new KaaAdminServiceException( String.valueOf(errorMessage.append("See server logs for details.")), ServiceErrorCode.GENERAL_ERROR ); } return result; } String createNewUser(org.kaaproject.kaa.common.dto.admin.UserDto user, boolean doSendTempPassword) throws Exception { checkFieldUniquieness( user.getUsername(), userFacade.getAll().stream().map(u -> u.getUsername()).collect(Collectors.toSet()), "userName" ); checkFieldUniquieness( user.getMail(), userFacade.getAll().stream().map(u -> u.getMail()).collect(Collectors.toSet()), "email" ); CreateUserResult result = saveUser(user, doSendTempPassword); user.setExternalUid(result.getUserId().toString()); return result.getPassword(); } void editUserFacadeUser(org.kaaproject.kaa.common.dto.admin.UserDto user) throws KaaAdminServiceException, ControlServiceException { User storedUserOld = userFacade.findByUserName(user.getUsername()); Utils.checkNotNull(storedUserOld); user.setExternalUid(String.valueOf(storedUserOld.getId())); UserDto storedUserNew = controlService.getUser(user.getId()); Utils.checkNotNull(storedUserNew); if (!getCurrentUser().getAuthority().equals(KaaAuthorityDto.KAA_ADMIN)) { checkTenantId(storedUserNew.getTenantId()); } storedUserOld.setMail(user.getMail()); storedUserOld.setFirstName(user.getFirstName()); storedUserOld.setLastName(user.getLastName()); userFacade.save(storedUserOld); } org.kaaproject.kaa.common.dto.admin.UserDto editControlServiceUser( org.kaaproject.kaa.common.dto.admin.UserDto user) throws KaaAdminServiceException, ControlServiceException { if (!isEmpty(getTenantId())) { user.setTenantId(getTenantId()); } else { user.setTenantId(user.getTenantId()); } org.kaaproject.kaa.common.dto.UserDto savedUser = controlService.editUser(user); return toUser(savedUser); } void checkCreateUserPermission(UserDto user) throws KaaAdminServiceException { if (user.getAuthority().equals(KaaAuthorityDto.TENANT_ADMIN)) { checkAuthority(KaaAuthorityDto.KAA_ADMIN); } else { checkAuthority(KaaAuthorityDto.TENANT_ADMIN); if (!isEmpty(user.getTenantId())) { checkTenantId(user.getTenantId()); } } } void checkEditUserPermission(UserDto user) throws KaaAdminServiceException, ControlServiceException { checkUserId(user.getId()); } void setPluginJsonConfigurationFromRaw(PluginDto plugin, PluginType type) { PluginInfoDto pluginInfo = pluginsInfo.get(type).get(plugin.getPluginClassName()); byte[] rawConfiguration = plugin.getRawConfiguration(); String jsonConfiguration = GenericAvroConverter.toJson( rawConfiguration, pluginInfo.getFieldConfiguration().getSchema()); plugin.setJsonConfiguration(jsonConfiguration); } void setPluginRawConfigurationFromJson(PluginDto plugin, PluginType type) { LOG.trace("Updating plugin {} configuration using info {}", plugin, pluginsInfo.get(type)); PluginInfoDto pluginInfo = pluginsInfo.get(type).get(plugin.getPluginClassName()); if (pluginInfo == null) { LOG.error( "Plugin configuration for class name {} is not found", plugin.getPluginClassName()); throw new InvalidParameterException( "Plugin configuration for class name " + plugin.getPluginClassName() + " is not found"); } byte[] rawConfiguration = GenericAvroConverter.toRawData( plugin.getJsonConfiguration(), pluginInfo.getFieldConfiguration() .getSchema()); plugin.setRawConfiguration(rawConfiguration); } CtlSchemaFormDto toCtlSchemaForm(CTLSchemaDto ctlSchema, ConverterType converterType) throws KaaAdminServiceException { try { CtlSchemaFormDto ctlSchemaForm = new CtlSchemaFormDto(ctlSchema); SchemaFormAvroConverter converter = getCtlSchemaConverterForScope( ctlSchemaForm.getMetaInfo().getTenantId(), ctlSchemaForm.getMetaInfo().getApplicationId(), converterType); RecordField form = converter.createSchemaFormFromSchema(ctlSchema.getBody()); ctlSchemaForm.setSchema(form); List<Integer> availableVersions = controlService.getAllCtlSchemaVersionsByFqnTenantIdAndApplicationId( ctlSchema.getMetaInfo().getFqn(), ctlSchema.getMetaInfo().getTenantId(), ctlSchema.getMetaInfo().getApplicationId()); availableVersions = availableVersions == null ? Collections.<Integer>emptyList() : availableVersions; Collections.sort(availableVersions); ctlSchemaForm.getMetaInfo().setVersions(availableVersions); return ctlSchemaForm; } catch (Exception cause) { throw Utils.handleException(cause); } } SchemaFormAvroConverter getCtlSchemaConverterForScope(String tenantId, String applicationId, ConverterType converterType) throws KaaAdminServiceException { try { if (isEmpty(tenantId)) { return getCtlSchemaConverterForSystem(converterType); } if (isEmpty(applicationId)) { return getCtlSchemaConverterForTenant(tenantId, converterType); } return getCtlSchemaConverterForApplication(tenantId, applicationId, converterType); } catch (Exception cause) { throw Utils.handleException(cause); } } private SchemaFormAvroConverter getCtlSchemaConverterForSystem(ConverterType converterType) throws KaaAdminServiceException { try { return createSchemaConverterFromCtlTypes( controlService.getAvailableCtlSchemaVersionsForSystem(), converterType); } catch (Exception cause) { throw Utils.handleException(cause); } } private SchemaFormAvroConverter getCtlSchemaConverterForTenant(String tenantId, ConverterType converterType) throws KaaAdminServiceException { try { return createSchemaConverterFromCtlTypes( controlService.getAvailableCtlSchemaVersionsForTenant(tenantId), converterType); } catch (Exception cause) { throw Utils.handleException(cause); } } private SchemaFormAvroConverter getCtlSchemaConverterForApplication(String tenantId, String applicationId, ConverterType converterType) throws KaaAdminServiceException { try { return createSchemaConverterFromCtlTypes( controlService.getAvailableCtlSchemaVersionsForApplication(tenantId, applicationId), converterType); } catch (Exception cause) { throw Utils.handleException(cause); } } private SchemaFormAvroConverter createSchemaConverterFromCtlTypes(final Map<Fqn, List<Integer>> ctlTypes, ConverterType converterType) throws KaaAdminServiceException { try { CtlSource ctlSource = new CtlSource() { @Override public Map<Fqn, List<Integer>> getCtlTypes() { return ctlTypes; } }; if (converterType == CONFIGURATION_FORM_AVRO_CONVERTER) { return new ConfigurationSchemaFormAvroConverter(ctlSource); } else { return new SchemaFormAvroConverter(ctlSource); } } catch (Exception cause) { throw Utils.handleException(cause); } } void convertToSchemaForm(AbstractSchemaDto dto, SchemaFormAvroConverter converter) throws IOException { Schema schema = new Schema.Parser().parse(dto.getSchema()); RecordField schemaForm = converter.createSchemaFormFromSchema(schema); dto.setSchemaForm(schemaForm); } void convertToStringSchema(AbstractSchemaDto dto, SchemaFormAvroConverter converter) throws Exception { Schema schema = converter.createSchemaFromSchemaForm(dto.getSchemaForm()); validateRecordSchema(schema); String schemaString = SchemaFormAvroConverter.createSchemaString(schema, true); dto.setSchema(schemaString); } void setPluginRawConfigurationFromForm(PluginDto plugin) throws IOException { RecordField fieldConfiguration = plugin.getFieldConfiguration(); GenericRecord record = FormAvroConverter.createGenericRecordFromRecordField( fieldConfiguration); GenericAvroConverter<GenericRecord> converter = new GenericAvroConverter<>(record.getSchema()); byte[] rawConfiguration = converter.encode(record); plugin.setRawConfiguration(rawConfiguration); } void setPluginFormConfigurationFromRaw(PluginDto plugin, PluginType type) throws IOException { LOG.trace("Updating plugin {} configuration", plugin); PluginInfoDto pluginInfo = pluginsInfo.get(type).get(plugin.getPluginClassName()); byte[] rawConfiguration = plugin.getRawConfiguration(); GenericAvroConverter<GenericRecord> converter = new GenericAvroConverter<>( pluginInfo.getFieldConfiguration().getSchema()); GenericRecord record = converter.decodeBinary(rawConfiguration); RecordField formData = FormAvroConverter.createRecordFieldFromGenericRecord(record); plugin.setFieldConfiguration(formData); } protected org.kaaproject.kaa.common.dto.admin.UserDto toUser(UserDto tenantUser) { User user = userFacade.findById(Long.valueOf(tenantUser.getExternalUid())); org.kaaproject.kaa.common.dto.admin.UserDto result = new org.kaaproject.kaa.common.dto.admin.UserDto( user.getId().toString(), user.getUsername(), user.getFirstName(), user.getLastName(), user.getMail(), KaaAuthorityDto.valueOf(user.getAuthorities().iterator().next().getAuthority())); result.setId(tenantUser.getId()); result.setTenantId(tenantUser.getTenantId()); return result; } }