diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt index 5925cab49..4c903ef38 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt @@ -133,15 +133,17 @@ class ZOSMFConnectionConfigurable : BoundSearchableConfigurable("z/OSMF Connecti } /** Generates a connection removal warning message that is used for working sets */ - private fun generateRemoveWarningMessage(wsUsages: List, wsType: String): StringBuilder { - val warningMessageBuilder = - StringBuilder("The following $wsType working sets use selected connections:
") - wsUsages.forEach { wsConfig -> - warningMessageBuilder.append(wsConfig.name).append(", ") + companion object { + fun generateRemoveWarningMessage(wsUsages: List, wsType: String): StringBuilder { + val warningMessageBuilder = + StringBuilder("The following $wsType working sets use selected connections:
") + wsUsages.forEach { wsConfig -> + warningMessageBuilder.append(wsConfig.name).append(", ") + } + warningMessageBuilder.setLength(warningMessageBuilder.length - 2) + warningMessageBuilder.append(".
") + return warningMessageBuilder } - warningMessageBuilder.setLength(warningMessageBuilder.length - 2) - warningMessageBuilder.append(".
") - return warningMessageBuilder } /** Remove connections with the warning before they are deleted */ diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt new file mode 100644 index 000000000..d51447cd3 --- /dev/null +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt @@ -0,0 +1,260 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package org.zowe.explorer.config.connect.ui.zosmf + +import com.intellij.icons.AllIcons +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.MessageDialogBuilder +import com.intellij.ui.dsl.builder.* +import org.zowe.explorer.common.ui.DialogMode +import org.zowe.explorer.common.ui.StatefulDialog +import org.zowe.explorer.common.ui.showUntilDone +import org.zowe.explorer.config.connect.ConnectionConfig +import org.zowe.explorer.config.connect.CredentialService +import org.zowe.explorer.config.connect.whoAmI +import org.zowe.explorer.dataops.DataOpsManager +import org.zowe.explorer.dataops.operations.InfoOperation +import org.zowe.explorer.dataops.operations.ZOSInfoOperation +import org.zowe.explorer.utils.* +import org.zowe.explorer.utils.crudable.Crudable +import org.zowe.explorer.utils.crudable.find +import org.zowe.explorer.zowe.ZOWE_CONFIG_NAME +import org.zowe.kotlinsdk.annotations.ZVersion +import java.awt.Component +import java.util.* +import javax.swing.JCheckBox +import javax.swing.JComponent +import javax.swing.JPasswordField +import javax.swing.JTextField + +/** Dialog to add a new zowe config file */ +class ZoweTeamConfigDialog( + private val crudable: Crudable, + override var state: ConnectionDialogState = ConnectionDialogState(), + val project: Project? = null +) : StatefulDialog(project) { + + /** + * Private field + * In case of DialogMode.UPDATE takes the last successful state from crudable, takes default state otherwise + */ + private val lastSuccessfulState: ConnectionDialogState = + if (state.mode == DialogMode.UPDATE) crudable.find { it.uuid == state.connectionUuid } + .findAny() + .orElseGet { state.connectionConfig } + .toDialogState(crudable) else ConnectionDialogState() + + companion object { + + /** Show Test connection dialog and test the connection regarding the dialog state. + * The method checks whether connection succeeds for specified user/password.. + * */ + @JvmStatic + fun showAndTestConnection( + crudable: Crudable, + parentComponent: Component? = null, + project: Project? = null, + initialState: ConnectionDialogState + ): ConnectionDialogState? { + return showUntilDone( + initialState = initialState, + factory = { ZoweTeamConfigDialog(crudable, initialState, project) }, + test = { state -> + val newTestedConnConfig: ConnectionConfig + if (initialState.mode == DialogMode.UPDATE) { + val newState = state.clone() + newState.initEmptyUuids(crudable) + newTestedConnConfig = ConnectionConfig( + newState.connectionUuid, + newState.connectionName, + newState.connectionUrl, + newState.isAllowSsl, + newState.zVersion + ) + CredentialService.instance.setCredentials( + connectionConfigUuid = newState.connectionUuid, + username = newState.username, + password = newState.password + ) + } else { + state.initEmptyUuids(crudable) + newTestedConnConfig = state.connectionConfig + CredentialService.instance.setCredentials( + connectionConfigUuid = state.connectionUuid, + username = state.username, + password = state.password + ) + } + val throwable = + runTask(title = "Testing Connection to ${newTestedConnConfig.url}", project = project) { + return@runTask try { + runCatching { + service().performOperation(InfoOperation(newTestedConnConfig), it) + }.onSuccess { + val systemInfo = + service().performOperation(ZOSInfoOperation(newTestedConnConfig)) + state.zVersion = when (systemInfo.zosVersion) { + "04.25.00" -> ZVersion.ZOS_2_2 + "04.26.00" -> ZVersion.ZOS_2_3 + "04.27.00" -> ZVersion.ZOS_2_4 + "04.28.00" -> ZVersion.ZOS_2_5 + else -> ZVersion.ZOS_2_1 + } + }.onFailure { + throw it + } + null + } catch (t: Throwable) { + t + } + } + if (throwable != null) { + state.mode = DialogMode.UPDATE + val confirmMessage = "Do you want to add it anyway?" + val tMessage = throwable.message?.let { + if (it.contains("Exception")) { + it.substring(it.lastIndexOf(":") + 2) + .replaceFirstChar { c -> if (c.isLowerCase()) c.titlecase(Locale.getDefault()) else c.toString() } + } else { + it + } + } + val message = if (tMessage != null) { + "$tMessage\n\n$confirmMessage" + } else { + confirmMessage + } + val addAnyway = MessageDialogBuilder + .yesNo( + title = "Error Creating Connection", + message = message + ).icon(AllIcons.General.ErrorDialog) + .run { + if (parentComponent != null) { + ask(parentComponent) + } else { + ask(project) + } + } + addAnyway + } else { + runTask(title = "Retrieving user information", project = project) { + state.owner = whoAmI(newTestedConnConfig) ?: "" + } + true + } + } + ) + } + } + + private val initialState = state.clone() + + private lateinit var urlTextField: JTextField + + private lateinit var sslCheckbox: JCheckBox + + private lateinit var globalConfigCheckbox: JCheckBox + + init { + isResizable = false + } + + /** Create dialog with the fields */ + override fun createCenterPanel(): JComponent { + val sameWidthLabelsGroup = "CONNECTION_DIALOG_LABELS_WIDTH_GROUP" + + state.zoweConfigPath = "${project?.basePath}/${ZOWE_CONFIG_NAME}" + var connectionName = "zowe-".plus(project?.name) + return panel { + row { + label("Connection name") + .widthGroup(sameWidthLabelsGroup) + if (state.zoweConfigPath == null) { + textField() + .bindText(state::connectionName) + .enabled(false) + .text(connectionName) + .validationOnApply { + it.text = it.text.trim() + validateForBlank(it) ?: validateConnectionName( + it, + initialState.connectionName.ifBlank { null }, + crudable + ) + } + .focused() + .align(AlignX.FILL) + } else { + textField() + .bindText(state::connectionName) + .enabled(false) + .text(connectionName) + .applyToComponent { isEditable = false } + .align(AlignX.FILL) + } + } + row { + label("Connection URL: ") + .widthGroup(sameWidthLabelsGroup) + textField() + .bindText(state::connectionUrl) + .validationOnApply { + it.text = it.text.trim().removeTrailingSlashes() + validateForBlank(it) ?: validateZosmfUrl(it) + } + .also { urlTextField = it.component } + .align(AlignX.FILL) + } + row { + label("Username") + .widthGroup(sameWidthLabelsGroup) + ( + cell(JPasswordField()) + ) + .bindText(state::username) + .validationOnApply { + validateForBlank(String(it.password).trim(), it) + } + .onApply { + state.username = state.username.trim().uppercase() + } + .align(AlignX.FILL) + } + row { + label("Password: ") + .widthGroup(sameWidthLabelsGroup) + cell(JPasswordField()) + .bindText(state::password) + .validationOnApply { validateForBlank(it) } + .align(AlignX.FILL) + } + indent { + row { + checkBox("Accept self-signed SSL certificates") + .bindSelected(state::isAllowSsl) + .also { sslCheckbox = it.component } + } + row { + checkBox("Create global Zowe Team Configuration file") + .enabled(false) + } + } + } + .withMinimumWidth(500) + } + + init { + init() + title = "Add Zowe Team Configuration file" + } +} diff --git a/src/main/kotlin/org/zowe/explorer/explorer/actions/AddZoweTeamConfigAction.kt b/src/main/kotlin/org/zowe/explorer/explorer/actions/AddZoweTeamConfigAction.kt new file mode 100644 index 000000000..b86737db7 --- /dev/null +++ b/src/main/kotlin/org/zowe/explorer/explorer/actions/AddZoweTeamConfigAction.kt @@ -0,0 +1,70 @@ +package org.zowe.explorer.explorer.actions + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.components.service +import com.intellij.openapi.vfs.VirtualFileManager +import org.zowe.explorer.config.configCrudable +import org.zowe.explorer.config.connect.CredentialService +import org.zowe.explorer.config.connect.ui.zosmf.ConnectionDialogState +import org.zowe.explorer.config.connect.ui.zosmf.ZoweTeamConfigDialog +import org.zowe.explorer.config.connect.ui.zosmf.initEmptyUuids +import org.zowe.explorer.utils.runReadActionInEdtAndWait +import org.zowe.explorer.zowe.ZOWE_CONFIG_NAME +import org.zowe.explorer.zowe.service.ZoweConfigService +import java.nio.file.Path + + +/** + * Action for adding zowe team config file through UI. + */ +class AddZoweTeamConfigAction : AnAction() { + + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.EDT + } + + override fun update(e: AnActionEvent) { + val project = e.project ?: let { + e.presentation.isEnabled = false + e.presentation.description = "Configuration file can only be created in the project" + return + } + val zoweConfigLocation = "${project.basePath}/$ZOWE_CONFIG_NAME" + runReadActionInEdtAndWait { + VirtualFileManager.getInstance().findFileByNioPath(Path.of(zoweConfigLocation)) + }?.let { + e.presentation.isEnabled = false + e.presentation.description = "$ZOWE_CONFIG_NAME already exists in the project" + } ?: return + } + + /** Shows zowe team config dialog */ + override fun actionPerformed(e: AnActionEvent) { + val state = ZoweTeamConfigDialog.showAndTestConnection( + crudable = configCrudable, + project = e.project, + initialState = ConnectionDialogState().initEmptyUuids(configCrudable) + ) + if (state != null) { + val connectionConfig = state.connectionConfig + val project = e.project ?: let { + e.presentation.isEnabled = false + e.presentation.description = "$ZOWE_CONFIG_NAME already exists in the project" + return + } + val zoweConfigService = project.service() + zoweConfigService.addZoweConfigFile(state) + + CredentialService.instance.setCredentials(connectionConfig.uuid, state.username, state.password) + configCrudable.add(connectionConfig) + } else { + return + } + } + + override fun isDumbAware(): Boolean { + return true + } +} diff --git a/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt b/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt index a6bed7f4a..7f2ca0c2b 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt @@ -56,6 +56,31 @@ fun showNotificationForAddUpdateZoweConfigIfNeeded(project: Project) { } } +/** + * Checks if zowe config has been deleted to be synchronized with crudable configs and show notification with delete action. + * @param project - project instance to check zoweConfig. + * @return Nothing. + */ +fun showNotificationForDeleteZoweConfigIfNeeded(project: Project) { + // if (zoweConfigState == ZoweConfigState.NOT_EXISTS) { + NotificationGroupManager.getInstance().getNotificationGroup(EXPLORER_NOTIFICATION_GROUP_ID) + .createNotification("Zowe config file has been deleted", NotificationType.INFORMATION) + .apply { + subscribe(ZOWE_CONFIG_CHANGED, object : ZoweConfigHandler { + override fun onConfigSaved(config: ZoweConfig, connectionConfig: ConnectionConfig) { + hideBalloon() + } + }) + addAction(object : DumbAwareAction("Delete Zowe Connection") { + override fun actionPerformed(e: AnActionEvent) { + project.service().deleteZoweConfig() + hideBalloon() + } + }).notify(project) + } + // } +} + /** * ZoweStartupActivity is needed to scan for the presence of a file at the project startup. * @author Valiantsin Krus diff --git a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt index c87fd5863..dad040798 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt @@ -13,6 +13,7 @@ package org.zowe.explorer.zowe.service import com.intellij.openapi.project.Project import com.intellij.util.messages.Topic import org.zowe.explorer.config.connect.ConnectionConfig +import org.zowe.explorer.config.connect.ui.zosmf.ConnectionDialogState import org.zowe.kotlinsdk.zowe.config.ZoweConfig @@ -69,6 +70,26 @@ interface ZoweConfigService { */ fun addOrUpdateZoweConfig (scanProject: Boolean = true, checkConnection: Boolean = true): ConnectionConfig? + /** + * Deletes connection config related to zoweConnection + * @return - Nothing. + */ + fun deleteZoweConfig() + + /** + * Creates zowe.schema.json for the currrent project and adds credentials to the secret store + * @param state - ConnectionDialogState for new zowe-connection + * @return - Nothing. + */ + fun addZoweConfigFile(state: ConnectionDialogState?) + + /** + * Checks all connections and removes linl to Zowe config file if it exists + * renames old connection if it is needed + * @return - Nothing. + */ + fun checkAndRemoveOldZoweConnection() + companion object { fun getInstance(project: Project): ZoweConfigService = project.getService(ZoweConfigService::class.java) } diff --git a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt index 0b858693e..22249ced6 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt @@ -10,6 +10,7 @@ package org.zowe.explorer.zowe.service +import com.google.gson.Gson import com.intellij.notification.Notification import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType @@ -18,23 +19,42 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.components.service import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectLocator +import com.intellij.openapi.ui.Messages import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.util.ReflectionUtil +import org.apache.commons.io.FileUtils import org.zowe.explorer.config.ConfigService import org.zowe.explorer.config.connect.* +import org.zowe.explorer.config.connect.ui.zosmf.ConnectionDialogState +import org.zowe.explorer.config.connect.ui.zosmf.ZOSMFConnectionConfigurable +import org.zowe.explorer.config.connect.ui.zosmf.toDialogState +import org.zowe.explorer.config.sandboxCrudable +import org.zowe.explorer.config.ws.FilesWorkingSetConfig +import org.zowe.explorer.config.ws.JesWorkingSetConfig import org.zowe.explorer.dataops.DataOpsManager +import org.zowe.explorer.dataops.operations.ChangePasswordOperation import org.zowe.explorer.dataops.operations.InfoOperation import org.zowe.explorer.dataops.operations.ZOSInfoOperation import org.zowe.explorer.explorer.EXPLORER_NOTIFICATION_GROUP_ID +import org.zowe.explorer.utils.* import org.zowe.explorer.utils.crudable.find -import org.zowe.explorer.utils.runReadActionInEdtAndWait -import org.zowe.explorer.utils.runTask -import org.zowe.explorer.utils.sendTopic +import org.zowe.explorer.utils.crudable.getAll import org.zowe.explorer.zowe.ZOWE_CONFIG_NAME +import org.zowe.kotlinsdk.ChangePassword import org.zowe.kotlinsdk.annotations.ZVersion -import org.zowe.kotlinsdk.zowe.config.ZoweConfig -import org.zowe.kotlinsdk.zowe.config.parseConfigJson -import java.nio.file.Path +import org.zowe.kotlinsdk.zowe.config.* +import org.zowe.kotlinsdk.zowe.config.ZoweConfig.Companion.ZOWE_SECURE_ACCOUNT +import org.zowe.kotlinsdk.zowe.config.ZoweConfig.Companion.ZOWE_SERVICE_BASE +import org.zowe.kotlinsdk.zowe.config.ZoweConfig.Companion.ZOWE_SERVICE_NAME +import java.io.* +import java.net.URI +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import java.nio.file.* import java.util.* +import java.util.regex.Matcher +import java.util.regex.Pattern import java.util.stream.Collectors const val ZOWE_CONFIG_NOTIFICATION_GROUP_ID = "org.zowe.explorerzowe.service.ZoweConfigNotificationGroupId" @@ -207,6 +227,162 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService } } + /** + * @see ZoweConfigService.deleteZoweConfig + */ + override fun deleteZoweConfig() { + try { + val zoweConnection = findExistingConnection() ?: throw Exception("Cannot get Zowe config") + + val filesWorkingSets = sandboxCrudable.getAll().toMutableList() + val filesWsUsages = filesWorkingSets.filter { filesWsConfig -> + filesWsConfig.connectionConfigUuid == zoweConnection.toDialogState(configCrudable).connectionConfig.uuid + } + + val jesWorkingSet = sandboxCrudable.getAll().toMutableList() + val jesWsUsages = jesWorkingSet.filter { jesWsConfig -> + jesWsConfig.connectionConfigUuid == zoweConnection.toDialogState(configCrudable).connectionConfig.uuid + } + + if (filesWsUsages.isEmpty() && jesWsUsages.isEmpty()) { + CredentialService.instance.clearCredentials(zoweConnection.uuid) + configCrudable.delete(zoweConnection) + return + } + + val warningMessageBuilder = StringBuilder() + if (filesWsUsages.isNotEmpty()) { + warningMessageBuilder.append(ZOSMFConnectionConfigurable.generateRemoveWarningMessage(filesWsUsages, "Files")) + } + if (jesWsUsages.isNotEmpty()) { + warningMessageBuilder.append(ZOSMFConnectionConfigurable.generateRemoveWarningMessage(jesWsUsages, "JES")) + } + warningMessageBuilder.append("
Do you really want to remove it?") + + val ret = Messages.showOkCancelDialog( + warningMessageBuilder.toString(), + "Warning", + "Yes", + "Cancel", + Messages.getWarningIcon() + ) + + if (ret == Messages.OK) { + CredentialService.instance.clearCredentials(zoweConnection.uuid) + configCrudable.delete(zoweConnection) + } + + } catch (e: Exception) { + notifyError(e) + } + } + + /** + * @see ZoweConfigService.addZoweConfigFile + */ + override fun addZoweConfigFile(state: ConnectionDialogState?) { + checkAndRemoveOldZoweConnection() + + val schemaFileName = "${myProject.basePath}/zowe.schema.json" + val jsonFileName = "${myProject.basePath}/${ZOWE_CONFIG_NAME}" + + val callerClass = ReflectionUtil.getGrandCallerClass()?.classLoader + val url = callerClass?.getResource("files/$ZOWE_CONFIG_NAME"); + val urlSchema = callerClass?.getResource("files/zowe.schema.json"); + + val f: File = File(schemaFileName) + if (!f.exists()) { + FileUtils.copyURLToFile(urlSchema, File(schemaFileName)); + } + + val env: Map = HashMap() + val array = url.toString().split("!") + val fs: FileSystem = FileSystems.newFileSystem(URI.create(array[0]), env) + val path: Path = fs.getPath(array[1]) + + val charset: Charset = StandardCharsets.UTF_8 + + val urlRegex = + "(https?:\\/\\/)(www\\.)?([-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})\b?([-a-zA-Z0-9()@:%_\\+.~#?&\\/\\/=]*)" + val pattern: Pattern = Pattern.compile(urlRegex) + val matcher: Matcher = pattern.matcher(state?.connectionUrl) + + var host = "localhost" + var port = "443" + if (matcher.matches()) { + host = matcher.group(3) + port = matcher.group(4).substring(1) + } + + var content = String(Files.readAllBytes(path), charset) + content = content.replace("".toRegex(), port) + content = content.replace("".toRegex(), "\"$host\"") + content = content.replace("".toRegex(), (!state?.isAllowSsl!!).toString()) + Files.write(Paths.get(jsonFileName), content.toByteArray(charset)) + fs.close() + + //This code should be moved to Kotlin SDK ->> + runWriteActionOnWriteThread { + var ret: Map<*, *> + + val keytar: KeytarWrapper = DefaultKeytarWrapper() + var configMap = keytar.getCredentials(ZOWE_SERVICE_BASE) + if (configMap.isNotEmpty() && configMap.containsKey(ZOWE_SECURE_ACCOUNT)) { + ret = Gson().fromJson(configMap[ZOWE_SECURE_ACCOUNT]?.decodeFromBase64(), Map::class.java) + } else { + var result = "" + var configNumber = 1 + do { + configMap = keytar.getCredentials("${ZOWE_SERVICE_NAME}-${configNumber}") + val account = "${ZOWE_SECURE_ACCOUNT}-${configNumber++}" + if (configMap.containsKey(account)) { + result += configMap[account] + } + } while (configMap.isNotEmpty()) + ret = Gson().fromJson(result.decodeFromBase64(), Map::class.java) + } + + val configCredentials = ret.toMutableMap() + + val configCredentialsMap = mutableMapOf() + configCredentialsMap["profiles.base.properties.user"] = state.username.uppercase() + configCredentialsMap["profiles.base.properties.password"] = state.password.uppercase() + + configCredentials[jsonFileName] = configCredentialsMap + val passwordToSave = Gson().toJson(configCredentials) + val osName = System.getProperty("os.name") + val encodedObjectToSave = passwordToSave.encodeToBase64() + if (passwordToSave.length < WINDOWS_MAX_PASSWORD_LENGTH || !osName.contains("Windows")) { + keytar.setPassword(ZOWE_SERVICE_BASE, ZOWE_SECURE_ACCOUNT, encodedObjectToSave) + } else { + keytar.deletePassword(ZOWE_SERVICE_BASE, ZOWE_SECURE_ACCOUNT) + encodedObjectToSave.chunked(WINDOWS_MAX_PASSWORD_LENGTH).forEachIndexed { i, chunk -> + keytar.setPassword(ZOWE_SERVICE_BASE, "$ZOWE_SECURE_ACCOUNT-${i + 1}", chunk) + } + } + } + //<<- that code should be moved to Kotlin SDK + } + + override fun checkAndRemoveOldZoweConnection(){ + var allConnections = configCrudable.getAll().toList() + var allConnectionsNames : MutableList = allConnections.map{it.name}.toMutableList() + + allConnections.filter{it.zoweConfigPath != null }.forEach{ + var index = 1 + var newName= it.name + while ( allConnectionsNames.contains(newName)) + { + newName = it.name.plus(index.toString()) + index++ + } + allConnectionsNames.add(newName) + it.name = newName + it.zoweConfigPath=null + configCrudable.update(it) + } + } + /** * Converts ZoweConfig to ConnectionConfig. * @param uuid - uuid returned connection. diff --git a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt index 74b54c8e2..b964694dd 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt @@ -17,6 +17,7 @@ import com.intellij.openapi.vfs.newvfs.events.VFileEvent import org.zowe.explorer.utils.runIfTrue import org.zowe.explorer.utils.service import org.zowe.explorer.zowe.showNotificationForAddUpdateZoweConfigIfNeeded +import org.zowe.explorer.zowe.showNotificationForDeleteZoweConfigIfNeeded /** * ZoweFileListener is needed for listening of vfs changes topic and @@ -39,6 +40,7 @@ class ZoweFileListener: BulkFileListener { runIfTrue(file.name == "zowe.config.json") { val projectForFile = ProjectLocator.getInstance().guessProjectForFile(file) ?: return if (e is VFileDeleteEvent) { + showNotificationForDeleteZoweConfigIfNeeded(projectForFile) projectForFile.service().zoweConfig = null } else if(!isBefore) { showNotificationForAddUpdateZoweConfigIfNeeded(projectForFile) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 0b467f8d9..e4f217c57 100755 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -429,6 +429,12 @@ Thank you for considering IBA Group for your mainframe needs. text="Connection" icon="AllIcons.Javaee.WebService"/> + + @@ -741,6 +747,7 @@ Thank you for considering IBA Group for your mainframe needs. icon="AllIcons.General.Add" popup="true"> + diff --git a/src/main/resources/files/zowe.config.json b/src/main/resources/files/zowe.config.json new file mode 100644 index 000000000..4393285fd --- /dev/null +++ b/src/main/resources/files/zowe.config.json @@ -0,0 +1,46 @@ +{ + "$schema": "./zowe.schema.json", + "profiles": { + "zosmf": { + "type": "zosmf", + "properties": { + "port": + }, + "secure": [] + }, + "tso": { + "type": "tso", + "properties": { + "account": "", + "codePage": "1047", + "logonProcedure": "IZUFPROC" + }, + "secure": [] + }, + "ssh": { + "type": "ssh", + "properties": { + "port": 22 + }, + "secure": [] + }, + "base": { + "type": "base", + "properties": { + "host": , + "rejectUnauthorized": + }, + "secure": [ + "user", + "password" + ] + } + }, + "defaults": { + "zosmf": "zosmf", + "tso": "tso", + "ssh": "ssh", + "base": "base" + }, + "autoStore": true +} \ No newline at end of file diff --git a/src/main/resources/files/zowe.schema.json b/src/main/resources/files/zowe.schema.json new file mode 100644 index 000000000..62a47fbae --- /dev/null +++ b/src/main/resources/files/zowe.schema.json @@ -0,0 +1,350 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$version": "1.0", + "type": "object", + "description": "Zowe configuration", + "properties": { + "profiles": { + "type": "object", + "description": "Mapping of profile names to profile configurations", + "patternProperties": { + "^\\S*$": { + "type": "object", + "description": "Profile configuration object", + "properties": { + "type": { + "description": "Profile type", + "type": "string", + "enum": [ + "zosmf", + "tso", + "ssh", + "base" + ] + }, + "properties": { + "description": "Profile properties object", + "type": "object" + }, + "profiles": { + "description": "Optional subprofile configurations", + "type": "object", + "$ref": "#/properties/profiles" + }, + "secure": { + "description": "Secure property names", + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": false + } + }, + "then": { + "properties": { + "properties": { + "title": "Missing profile type" + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "zosmf" + } + } + }, + "then": { + "properties": { + "properties": { + "type": "object", + "title": "z/OSMF Profile", + "description": "z/OSMF Profile", + "properties": { + "host": { + "type": "string", + "description": "The z/OSMF server host name." + }, + "port": { + "type": "number", + "description": "The z/OSMF server port.", + "default": 443 + }, + "user": { + "type": "string", + "description": "Mainframe (z/OSMF) user name, which can be the same as your TSO login." + }, + "password": { + "type": "string", + "description": "Mainframe (z/OSMF) password, which can be the same as your TSO password." + }, + "rejectUnauthorized": { + "type": "boolean", + "description": "Reject self-signed certificates.", + "default": true + }, + "certFile": { + "type": "string", + "description": "The file path to a certificate file to use for authentication" + }, + "certKeyFile": { + "type": "string", + "description": "The file path to a certificate key file to use for authentication" + }, + "basePath": { + "type": "string", + "description": "The base path for your API mediation layer instance. Specify this option to prepend the base path to all z/OSMF resources when making REST requests. Do not specify this option if you are not using an API mediation layer." + }, + "protocol": { + "type": "string", + "description": "The protocol used (HTTP or HTTPS)", + "default": "https", + "enum": [ + "http", + "https" + ] + }, + "encoding": { + "type": "string", + "description": "The encoding for download and upload of z/OS data set and USS files. The default encoding if not specified is IBM-1047." + }, + "responseTimeout": { + "type": "number", + "description": "The maximum amount of time in seconds the z/OSMF Files TSO servlet should run before returning a response. Any request exceeding this amount of time will be terminated and return an error. Allowed values: 5 - 600" + } + }, + "required": [] + }, + "secure": { + "items": { + "enum": [ + "user", + "password" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "tso" + } + } + }, + "then": { + "properties": { + "properties": { + "type": "object", + "title": "TSO Profile", + "description": "z/OS TSO/E User Profile", + "properties": { + "account": { + "type": "string", + "description": "Your z/OS TSO/E accounting information." + }, + "characterSet": { + "type": "string", + "description": "Character set for address space to convert messages and responses from UTF-8 to EBCDIC.", + "default": "697" + }, + "codePage": { + "type": "string", + "description": "Codepage value for TSO/E address space to convert messages and responses from UTF-8 to EBCDIC.", + "default": "1047" + }, + "columns": { + "type": "number", + "description": "The number of columns on a screen.", + "default": 80 + }, + "logonProcedure": { + "type": "string", + "description": "The logon procedure to use when creating TSO procedures on your behalf.", + "default": "IZUFPROC" + }, + "regionSize": { + "type": "number", + "description": "Region size for the TSO/E address space.", + "default": 4096 + }, + "rows": { + "type": "number", + "description": "The number of rows on a screen.", + "default": 24 + } + }, + "required": [] + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "ssh" + } + } + }, + "then": { + "properties": { + "properties": { + "type": "object", + "title": "z/OS SSH Profile", + "description": "z/OS SSH Profile", + "properties": { + "host": { + "type": "string", + "description": "The z/OS SSH server host name." + }, + "port": { + "type": "number", + "description": "The z/OS SSH server port.", + "default": 22 + }, + "user": { + "type": "string", + "description": "Mainframe user name, which can be the same as your TSO login." + }, + "password": { + "type": "string", + "description": "Mainframe password, which can be the same as your TSO password." + }, + "privateKey": { + "type": "string", + "description": "Path to a file containing your private key, that must match a public key stored in the server for authentication" + }, + "keyPassphrase": { + "type": "string", + "description": "Private key passphrase, which unlocks the private key." + }, + "handshakeTimeout": { + "type": "number", + "description": "How long in milliseconds to wait for the SSH handshake to complete." + } + }, + "required": [] + }, + "secure": { + "items": { + "enum": [ + "user", + "password", + "keyPassphrase" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "base" + } + } + }, + "then": { + "properties": { + "properties": { + "type": "object", + "title": "Base Profile", + "description": "Base profile that stores values shared by multiple service profiles", + "properties": { + "host": { + "type": "string", + "description": "Host name of service on the mainframe." + }, + "port": { + "type": "number", + "description": "Port number of service on the mainframe." + }, + "user": { + "type": "string", + "description": "User name to authenticate to service on the mainframe." + }, + "password": { + "type": "string", + "description": "Password to authenticate to service on the mainframe." + }, + "rejectUnauthorized": { + "type": "boolean", + "description": "Reject self-signed certificates.", + "default": true + }, + "tokenType": { + "type": "string", + "description": "The type of token to get and use for the API. Omit this option to use the default token type, which is provided by 'zowe auth login'." + }, + "tokenValue": { + "type": "string", + "description": "The value of the token to pass to the API." + }, + "certFile": { + "type": "string", + "description": "The file path to a certificate file to use for authentication.\n\nNote: The CLI does not support certificate files that require a password. For more information, search Troubleshooting PEM Certificates in Zowe Docs." + }, + "certKeyFile": { + "type": "string", + "description": "The file path to a certificate key file to use for authentication" + } + }, + "required": [] + }, + "secure": { + "items": { + "enum": [ + "user", + "password", + "tokenValue" + ] + } + } + } + } + } + ] + } + } + }, + "defaults": { + "type": "object", + "description": "Mapping of profile types to default profile names", + "properties": { + "zosmf": { + "description": "Default zosmf profile", + "type": "string" + }, + "tso": { + "description": "Default tso profile", + "type": "string" + }, + "ssh": { + "description": "Default ssh profile", + "type": "string" + }, + "base": { + "description": "Default base profile", + "type": "string" + } + } + }, + "autoStore": { + "type": "boolean", + "description": "If true, values you enter when prompted are stored for future use" + } + } +} \ No newline at end of file