Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-241 Fixed writing to USS file with no access rights #309

Open
wants to merge 1 commit into
base: release/v2.1.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,22 @@ data class RemoteUssAttributes(
charset = DEFAULT_BINARY_CHARSET
)

/** Get file mode access number basing on the file owner and the available requesters to use the file */
private fun getAvailableFileModeAccessNum(): Int {
val hasFileOwnerInRequesters = requesters.any { requester ->
val savedOwner = CredentialService.getOwner(requester.connectionConfig)
val ownerOrUsername =
if (savedOwner == "") CredentialService.getUsername(requester.connectionConfig)
else savedOwner
/** Get file mode access number basing on the file owner and the available requesters/connection to use the file */
private fun getAvailableFileModeAccessNum(connConfig: ConnectionConfig? = null): Int {
val hasFileOwnerInRequesters = if (connConfig == null) {
requesters.any { requester ->
val savedOwner = CredentialService.getOwner(requester.connectionConfig)
val ownerOrUsername =
if (savedOwner == "") CredentialService.getUsername(requester.connectionConfig)
else savedOwner
ownerOrUsername.equals(owner, ignoreCase = true)
}
} else {
val savedOwner = CredentialService.getOwner(connConfig)
val ownerOrUsername = if (savedOwner == "")
CredentialService.getUsername(connConfig)
else
savedOwner
ownerOrUsername.equals(owner, ignoreCase = true)
}
return if (fileMode != null) {
Expand All @@ -130,16 +139,24 @@ data class RemoteUssAttributes(

val isWritable: Boolean
get() {
val mode = getAvailableFileModeAccessNum()
return mode == FileModeValue.WRITE.mode
|| mode == FileModeValue.WRITE_EXECUTE.mode
|| mode == FileModeValue.READ_WRITE.mode
|| mode == FileModeValue.READ_WRITE_EXECUTE.mode
return isWritableForConnection()
}

fun isWritableForConnection(connConfig: ConnectionConfig? = null): Boolean {
val mode = getAvailableFileModeAccessNum(connConfig)
return mode == FileModeValue.WRITE.mode
|| mode == FileModeValue.WRITE_EXECUTE.mode
|| mode == FileModeValue.READ_WRITE.mode
|| mode == FileModeValue.READ_WRITE_EXECUTE.mode
}

val isReadable: Boolean
get() {
val mode = getAvailableFileModeAccessNum()
return isReadableForConnection()
}

fun isReadableForConnection(connConfig: ConnectionConfig? = null): Boolean {
val mode = getAvailableFileModeAccessNum(connConfig)
return mode == FileModeValue.READ.mode
|| mode == FileModeValue.READ_WRITE.mode
|| mode == FileModeValue.READ_EXECUTE.mode
Expand Down
41 changes: 41 additions & 0 deletions src/main/kotlin/org/zowe/explorer/explorer/ui/UssFileNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ package org.zowe.explorer.explorer.ui

import com.intellij.ide.projectView.PresentationData
import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Iconable
import com.intellij.ui.AnimatedIcon
Expand All @@ -25,6 +28,9 @@ import org.zowe.explorer.common.message
import org.zowe.explorer.config.connect.ConnectionConfig
import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.dataops.attributes.RemoteUssAttributes
import org.zowe.explorer.dataops.content.service.SyncProcessService
import org.zowe.explorer.dataops.content.synchronizer.DocumentedSyncProvider
import org.zowe.explorer.dataops.content.synchronizer.SaveStrategy
import org.zowe.explorer.dataops.getAttributesService
import org.zowe.explorer.dataops.sort.SortQueryKeys
import org.zowe.explorer.explorer.ExplorerUnit
Expand Down Expand Up @@ -71,6 +77,41 @@ class UssFileNode(
)
}

/**
* This method is required to set the correct permissions to open USS file.
* @see ExplorerTreeNode.navigate
*/
override fun navigate(requestFocus: Boolean) {
val file = virtualFile ?: return
if (file.isWritable) {
val syncProvider = DocumentedSyncProvider(file, SaveStrategy.default(project))
val contentSynchronizer = DataOpsManager.getService().getContentSynchronizer(file)
val currentContent = runReadAction { syncProvider.retrieveCurrentContent() }
val previousContent = contentSynchronizer?.successfulContentStorage(syncProvider)
val needToUpload = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true
if (
!(currentContent contentEquals previousContent)
&& needToUpload
&& !SyncProcessService.getService().isFileSyncingNow(file)
) {
runCatching {
runInEdt {
FileEditorManager.getInstance(project).closeFile(file)
}
}
}
}

val originConnectionConfig = unit.connectionConfig
val attributes = DataOpsManager.getService().tryToGetAttributes(file)
if (attributes is RemoteUssAttributes) {
file.isReadable = attributes.isReadableForConnection(originConnectionConfig)
file.isWritable = attributes.isWritableForConnection(originConnectionConfig)
}

super.navigate(requestFocus)
}

override fun getVirtualFile(): MFVirtualFile {
return value
}
Expand Down
153 changes: 145 additions & 8 deletions src/test/kotlin/org/zowe/explorer/explorer/ui/UssFileNodeTestSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ import com.intellij.ide.util.treeView.AbstractTreeNode
import com.intellij.ide.util.treeView.TreeAnchorizer
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.impl.FileDocumentManagerBase.HARD_REF_TO_DOCUMENT_KEY
import com.intellij.openapi.fileTypes.FileTypes
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.showYesNoDialog
import com.intellij.openapi.vfs.VirtualFile
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.mockk.*
import org.zowe.explorer.config.connect.ConnectionConfig
import org.zowe.explorer.config.connect.ConnectionConfigBase
import org.zowe.explorer.dataops.DataOpsManager
Expand All @@ -43,11 +49,8 @@ import org.zowe.explorer.utils.isBeingEditingNow
import org.zowe.explorer.utils.runInEdtAndWait
import org.zowe.explorer.vfs.MFVirtualFile
import org.zowe.explorer.vfs.MFVirtualFileSystem
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.mockk.*
import org.zowe.kotlinsdk.FileMode
import org.zowe.kotlinsdk.UssFile
import java.time.LocalDateTime
import javax.swing.Icon
import javax.swing.tree.TreePath
Expand All @@ -70,19 +73,33 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
lateinit var dataOpsManagerService: TestDataOpsManagerImpl
lateinit var uiComponentManagerService: TestUIComponentManager
lateinit var ussFileNode: UssFileNode
lateinit var rootNode: ExplorerTreeNode<ConnectionConfig, Any>
var firstNode: ExplorerTreeNode<ConnectionConfig, Any>

beforeEach {
mockkObject(MFVirtualFileSystem)
every { MFVirtualFileSystem.instance } returns mockk()

projectMock = mockk<Project>()

fileMock = mockk()
every { fileMock.isDirectory } returns false
every { fileMock.isReadable } returns true
var isReadable = true
every { fileMock.isReadable } returns isReadable
every { fileMock setProperty "isReadable" value any<Boolean>() } propertyType Boolean::class answers {
isReadable = value
}
var isWritable = true
every { fileMock.isWritable } returns isWritable
every { fileMock.isWritable = any<Boolean>() } answers { isWritable = firstArg() }
every { fileMock.isValid } returns true
every { fileMock.fileType } returns FileTypes.UNKNOWN
every { fileMock.detectedLineSeparator } returns "\n"
every { fileMock.name } returns "navigate test"
every { fileMock.getUserData(HARD_REF_TO_DOCUMENT_KEY) } returns null
mockkStatic(VirtualFile::isBeingEditingNow)
every { fileMock.isBeingEditingNow() } returns false

projectMock = mockk()

treeStructureMock = mockk()
every { treeStructureMock.registerNode(any()) } returns mockk()
Expand All @@ -99,14 +116,50 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
}
}

explorerTreeNodeMock = mockk()

explorer = mockk()
every { explorer.componentManager } returns ApplicationManager.getApplication()

explorerUnitMock = mockk()
every { explorerUnitMock.explorer } returns explorer

rootNode =
object : ExplorerTreeNode<ConnectionConfig, Any>("rootNode", projectMock, null, mockk(), treeStructureMock) {
override fun update(presentation: PresentationData) {
TODO("Not yet implemented")
}

override fun getChildren(): MutableCollection<out AbstractTreeNode<*>> {
TODO("Not yet implemented")
}
}

every { explorerUnitMock.connectionConfig } returns ConnectionConfig()
firstNode = object : ExplorerUnitTreeNodeBase<ConnectionConfig, Any, ExplorerUnit<ConnectionConfig>>(
"firstNode",
projectMock,
rootNode,
explorerUnitMock,
treeStructureMock
) {
override fun update(presentation: PresentationData) {
TODO("Not yet implemented")
}

override fun getChildren(): MutableCollection<out AbstractTreeNode<*>> {
TODO("Not yet implemented")
}
}

explorerTreeNodeMock = spyk(
UssFileNode(
fileMock, projectMock,
firstNode,
explorerUnitMock, treeStructureMock
),
recordPrivateCalls = true
)
every { explorerTreeNodeMock.parent } answers { firstNode }

dataOpsManagerService = DataOpsManager.getService() as TestDataOpsManagerImpl

ussFileNode = spyk(
Expand All @@ -129,6 +182,12 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl() {
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer {
val contentSynchronizerMock = mockk<ContentSynchronizer>()
every { contentSynchronizerMock.isFileUploadNeeded(any()) } answers {
false
}
every { contentSynchronizerMock.successfulContentStorage(any()) } answers {
byteArrayOf(Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE)
}
every { contentSynchronizerMock.synchronizeWithRemote(any(), any()) } answers {
isSyncWithRemotePerformed = true
val syncProvider = firstArg() as SyncProvider
Expand All @@ -154,9 +213,28 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
}
should("perform navigate on file with failure due to permission denied") {
var isSyncWithRemotePerformed = false
val ussFileMock = mockk<UssFile>()
every { ussFileMock.name } returns "USSFileName"
every { ussFileMock.isDirectory } returns false
every { ussFileMock.size } returns 100
every { ussFileMock.uid } returns 500
every { ussFileMock.user } returns "user"
every { ussFileMock.gid } returns 110
every { ussFileMock.groupId } returns "guid"
every { ussFileMock.modificationTime } returns "modTime"
every { ussFileMock.target } returns "target"
every { ussFileMock.fileMode } returns FileMode(7, 5, 5, "")
dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl() {

override fun tryToGetAttributes(file: VirtualFile): FileAttributes {
return RemoteUssAttributes("rootPath", ussFileMock, "URL", ConnectionConfig())
}
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer {
val contentSynchronizerMock = mockk<ContentSynchronizer>()
every { contentSynchronizerMock.isFileUploadNeeded(any()) } answers { false }
every { contentSynchronizerMock.successfulContentStorage(any()) } answers {
byteArrayOf(Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE)
}
every { contentSynchronizerMock.synchronizeWithRemote(any(), any()) } answers {
isSyncWithRemotePerformed = true
val syncProvider = firstArg() as SyncProvider
Expand Down Expand Up @@ -189,8 +267,15 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
should("perform navigate on file with failure due to client is not authorized") {
var isSyncWithRemotePerformed = false
dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl() {
override fun tryToGetAttributes(file: VirtualFile): FileAttributes {
return RemoteUssAttributes("test", false, null, "test", mutableListOf())
}
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer {
val contentSynchronizerMock = mockk<ContentSynchronizer>()
every { contentSynchronizerMock.isFileUploadNeeded(any()) } answers { false }
every { contentSynchronizerMock.successfulContentStorage(any()) } answers {
byteArrayOf(Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE)
}
every { contentSynchronizerMock.synchronizeWithRemote(any(), any()) } answers {
isSyncWithRemotePerformed = true
val syncProvider = firstArg() as SyncProvider
Expand Down Expand Up @@ -218,6 +303,9 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
}
should("exit 'navigate' when 'getContentSynchronizer' returns null") {
dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl() {
override fun tryToGetAttributes(file: VirtualFile): FileAttributes {
return RemoteUssAttributes("test", false, null, "test", mutableListOf())
}
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer? {
return null
}
Expand Down Expand Up @@ -258,8 +346,15 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({

var isNavigateContinued = false
dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl() {
override fun tryToGetAttributes(file: VirtualFile): FileAttributes {
return RemoteUssAttributes("test", false, null, "test", mutableListOf())
}
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer {
val contentSynchronizerMock = mockk<ContentSynchronizer>()
every { contentSynchronizerMock.isFileUploadNeeded(any()) } answers { false }
every { contentSynchronizerMock.successfulContentStorage(any()) } answers {
byteArrayOf(Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE)
}
every { contentSynchronizerMock.synchronizeWithRemote(any(), any()) } answers {
isNavigateContinued = true
val syncProvider = firstArg() as SyncProvider
Expand All @@ -274,10 +369,52 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({
assertSoftly { isNavigateContinued shouldBe false }
}
should("perform navigate on a file that was already opened") {
every { fileMock.isWritable } returns false
firstNode = object : ExplorerTreeNode<ConnectionConfig, Any>(
"nullConnectionNode",
projectMock,
rootNode,
mockk<FileExplorer>(),
treeStructureMock
) {
override fun update(presentation: PresentationData) {
TODO("Not yet implemented")
}
override fun getChildren(): MutableCollection<out AbstractTreeNode<*>> {
TODO("Not yet implemented")
}
}

explorerTreeNodeMock = spyk(
UssFileNode(
fileMock, projectMock,
firstNode,
explorerUnitMock, treeStructureMock
),
recordPrivateCalls = true
)
every { explorerTreeNodeMock.parent } answers { firstNode }

ussFileNode = spyk(
UssFileNode(
fileMock,
projectMock,
explorerTreeNodeMock,
explorerUnitMock,
treeStructureMock
)
)
every { ussFileNode.update() } returns false
every { ussFileNode.virtualFile } returns fileMock

var isSyncWithRemotePerformed = false
dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl() {
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer {
val contentSynchronizerMock = mockk<ContentSynchronizer>()
every { contentSynchronizerMock.isFileUploadNeeded(any()) } answers { true }
every { contentSynchronizerMock.successfulContentStorage(any()) } answers {
byteArrayOf(Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE)
}
every { contentSynchronizerMock.synchronizeWithRemote(any(), any()) } answers {
isSyncWithRemotePerformed = true
val syncProvider = firstArg() as SyncProvider
Expand Down