mirror of
https://github.com/rustfs/console.git
synced 2026-09-01 15:17:45 +08:00
refact: 代码重构
This commit is contained in:
@@ -1,87 +1,18 @@
|
||||
<template>
|
||||
<n-button text v-if="total > 0" @click="toggleDrawer">
|
||||
<div v-if="pending.length" class="flex items-center gap-2">
|
||||
<n-spin :size="14" />
|
||||
<span>{{ t('In Progress', { total: total, deleting: deleting.length, completed: completed.length }) }}</span>
|
||||
</div>
|
||||
<div v-else>{{ t('Delete Completed', { completed: completed.length, failed: failed.length }) }}</div>
|
||||
</n-button>
|
||||
|
||||
<n-drawer v-model:show="showDrawer" :width="502">
|
||||
<n-drawer-content :title="t('Management')" closable>
|
||||
<div class="flex flex-col gap-4">
|
||||
<n-alert type="warning" :show-icon="false">
|
||||
<p><span class="text-red-500">{{ t('Browser Warning') }}</span></p>
|
||||
<p><span class="text-red-500">{{ t('Cache Warning') }}</span></p>
|
||||
</n-alert>
|
||||
<div v-if="total > 0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ total - (pending.length + deleting.length) }}/{{ total }}</div>
|
||||
<n-button text type="info" @click="clearTasks">{{ t('Clear Records') }}</n-button>
|
||||
</div>
|
||||
<n-progress type="line" :height="2" :percentage="percentage" :show-indicator="false" :processing="percentage < 100" />
|
||||
</div>
|
||||
<n-tabs type="line" animated v-model:value="tab">
|
||||
<n-tab-pane :tab="t('Pending', { count: pending.length })" name="pending">
|
||||
<object-delete-task-list :tasks="pending" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Deleting', { count: deleting.length })" name="deleting">
|
||||
<object-delete-task-list :tasks="deleting" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Completed', { count: completed.length })" name="completed">
|
||||
<object-delete-task-list :tasks="completed" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Failed', { count: failed.length })" name="failed">
|
||||
<object-delete-task-list :tasks="failed" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
<object-task-stats :tasks="tasks" processing-status="deleting" :on-clear-tasks="clearTasks">
|
||||
<template #task-list="{ tasks }">
|
||||
<object-delete-task-list :tasks="tasks" />
|
||||
</template>
|
||||
</object-task-stats>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDeleteTaskManagerStore } from '~/store/delete-tasks'
|
||||
|
||||
const { t } = useI18n()
|
||||
const showDrawer = ref(false)
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useDeleteTaskManagerStore } from '~/store/delete-tasks'
|
||||
|
||||
const store = useDeleteTaskManagerStore()
|
||||
const tasks = computed(() => store.tasks)
|
||||
|
||||
const total = computed(() => tasks.value.length)
|
||||
const percentage = computed(() => total.value === 0 ? 100 : Math.floor((completed.value.length / total.value) * 100))
|
||||
|
||||
const pending = computed(() => tasks.value.filter(task => task.status === 'pending'))
|
||||
const deleting = computed(() => tasks.value.filter(task => task.status === 'deleting'))
|
||||
const completed = computed(() => tasks.value.filter(task => task.status === 'completed'))
|
||||
const failed = computed(() => tasks.value.filter(task => task.status === 'failed'))
|
||||
|
||||
const tab = ref('pending')
|
||||
|
||||
// 根据任务执行状态切换任务列表,如果有进行中的任务则默认展示进行中,否则展示已完成
|
||||
watch(percentage, () => {
|
||||
if (deleting.value.length > 0) {
|
||||
tab.value = 'deleting'
|
||||
} else {
|
||||
tab.value = 'completed'
|
||||
}
|
||||
})
|
||||
|
||||
// 第一次加入任务时自动打开抽屉
|
||||
const autoOpened = ref(false)
|
||||
watch(total, (newVal, oldVal) => {
|
||||
if (!autoOpened.value && !showDrawer.value && oldVal === 0 && newVal > 0) {
|
||||
showDrawer.value = true
|
||||
autoOpened.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const toggleDrawer = () => {
|
||||
showDrawer.value = !showDrawer.value
|
||||
}
|
||||
|
||||
const clearTasks = () => {
|
||||
store.clearTasks()
|
||||
}
|
||||
|
||||
@@ -58,58 +58,81 @@ const loading = ref<boolean>(false)
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const textMimes = [
|
||||
'application/json', 'application/xml',
|
||||
'application/javascript', 'application/x-sh',
|
||||
'application/x-csh', 'application/x-python',
|
||||
'application/x-php', 'application/x-perl',
|
||||
'application/x-shellscript', 'application/x-ruby',
|
||||
'application/x-java', 'application/x-cpp',
|
||||
]
|
||||
// 文件类型配置
|
||||
const fileTypeConfig = {
|
||||
text: {
|
||||
mimes: [
|
||||
'application/json', 'application/xml',
|
||||
'application/javascript', 'application/x-sh',
|
||||
'application/x-csh', 'application/x-python',
|
||||
'application/x-php', 'application/x-perl',
|
||||
'application/x-shellscript', 'application/x-ruby',
|
||||
'application/x-java', 'application/x-cpp',
|
||||
],
|
||||
extensions: [
|
||||
'.txt', '.json', '.xml', '.js', '.sh', '.csh',
|
||||
'.py', '.php', '.pl', '.sh', '.rb', '.java',
|
||||
'.cpp', '.h', '.hpp', '.c', '.cc', '.hh',
|
||||
'.hxx', '.cxx', '.java', '.kt', '.scala',
|
||||
'.groovy', '.rs', '.go', '.dart', '.swift',
|
||||
'.m', '.mm', '.cs', '.ts', '.jsx', '.tsx',
|
||||
'.html', '.htm', '.css', '.scss', '.sass',
|
||||
'.less', '.styl', '.yaml', '.yml', '.toml',
|
||||
'.ini', '.cfg', '.conf', '.properties', '.md',
|
||||
'.markdown', '.rst', '.r',
|
||||
]
|
||||
},
|
||||
image: {
|
||||
extensions: [
|
||||
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg',
|
||||
]
|
||||
},
|
||||
audio: {
|
||||
extensions: [
|
||||
'.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.wma',
|
||||
]
|
||||
},
|
||||
pdf: {
|
||||
extensions: [
|
||||
'.pdf',
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const textExtensions = [
|
||||
'.txt', '.json', '.xml', '.js', '.sh', '.csh',
|
||||
'.py', '.php', '.pl', '.sh', '.rb', '.java',
|
||||
'.cpp', '.h', '.hpp', '.c', '.cc', '.hh',
|
||||
'.hxx', '.cxx', '.java', '.kt', '.scala',
|
||||
'.groovy', '.rs', '.go', '.dart', '.swift',
|
||||
'.m', '.mm', '.cs', '.ts', '.jsx', '.tsx',
|
||||
'.html', '.htm', '.css', '.scss', '.sass',
|
||||
'.less', '.styl', '.yaml', '.yml', '.toml',
|
||||
'.ini', '.cfg', '.conf', '.properties', '.md',
|
||||
'.markdown', '.rst', '.r',
|
||||
]
|
||||
// 文件类型判断函数
|
||||
function isFileType(contentType: string | undefined, fileName: string, type: keyof typeof fileTypeConfig): boolean {
|
||||
if (type === 'text') {
|
||||
return contentType?.startsWith('text/') ||
|
||||
fileTypeConfig.text.mimes.some(mime => contentType?.includes(mime)) ||
|
||||
fileTypeConfig.text.extensions.some(ext => fileName.endsWith(ext));
|
||||
}
|
||||
|
||||
const imageExtensions = [
|
||||
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg',
|
||||
]
|
||||
if (type === 'image') {
|
||||
return contentType?.startsWith('image/') ||
|
||||
fileTypeConfig.image.extensions.some(ext => fileName.endsWith(ext));
|
||||
}
|
||||
|
||||
const audioExtensions = [
|
||||
'.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.wma',
|
||||
]
|
||||
if (type === 'audio') {
|
||||
return contentType?.startsWith('audio/') ||
|
||||
fileTypeConfig.audio.extensions.some(ext => fileName.endsWith(ext));
|
||||
}
|
||||
|
||||
const pdfExtensions = [
|
||||
'.pdf',
|
||||
]
|
||||
if (type === 'pdf') {
|
||||
return contentType === 'application/pdf' ||
|
||||
fileTypeConfig.pdf.extensions.some(ext => fileName.endsWith(ext));
|
||||
}
|
||||
|
||||
// MIME 类型判断逻辑
|
||||
const isImage = computed(() => {
|
||||
return contentType.value?.startsWith('image/') || imageExtensions.some(ext => props.objectKey.endsWith(ext))
|
||||
})
|
||||
const isVideo = computed(() => contentType.value?.startsWith('video/') || false)
|
||||
const isAudio = computed(() => {
|
||||
return contentType.value?.startsWith('audio/') || audioExtensions.some(ext => props.objectKey.endsWith(ext))
|
||||
})
|
||||
const isPdf = computed(() => {
|
||||
return contentType.value === 'application/pdf' || pdfExtensions.some(ext => props.objectKey.endsWith(ext))
|
||||
})
|
||||
const isText = computed(() => {
|
||||
// 常见文本类型,可按需扩展
|
||||
return contentType.value?.startsWith('text/') || textMimes.some(mime => contentType.value?.includes(mime)) ||
|
||||
textExtensions.some(ext => props.objectKey.endsWith(ext))
|
||||
})
|
||||
return false;
|
||||
}
|
||||
|
||||
const canPreview = computed(() => isImage.value || isPdf.value || isText.value || isVideo.value || isAudio.value)
|
||||
// 使用新的判断函数
|
||||
const isImage = computed(() => isFileType(contentType.value, props.objectKey, 'image'));
|
||||
const isVideo = computed(() => contentType.value?.startsWith('video/') || false);
|
||||
const isAudio = computed(() => isFileType(contentType.value, props.objectKey, 'audio'));
|
||||
const isPdf = computed(() => isFileType(contentType.value, props.objectKey, 'pdf'));
|
||||
const isText = computed(() => isFileType(contentType.value, props.objectKey, 'text'));
|
||||
|
||||
const canPreview = computed(() => isImage.value || isPdf.value || isText.value || isVideo.value || isAudio.value);
|
||||
|
||||
// 当 show 为 true 时触发加载逻辑
|
||||
watch(() => props.show, async (newVal) => {
|
||||
@@ -128,12 +151,12 @@ async function loadPreview() {
|
||||
|
||||
try {
|
||||
// 获取预签名URL
|
||||
const url = await getSignedUrl(objectKey.value)
|
||||
const url = await getSignedUrl(props.objectKey)
|
||||
|
||||
previewUrl.value = url
|
||||
|
||||
// 先使用 HEAD 请求获取 Content-Type
|
||||
const response = await headObject(objectKey.value)
|
||||
const response = await headObject(props.objectKey)
|
||||
|
||||
const ctype = response?.ContentType
|
||||
contentType.value = ctype || 'application/octet-stream'
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<n-button text v-if="total > 0" @click="toggleDrawer">
|
||||
<div v-if="pending.length" class="flex items-center gap-2">
|
||||
<n-spin :size="14" />
|
||||
<span>{{ t('In Progress', { total: total, processing: processing.length, completed: completed.length }) }}</span>
|
||||
</div>
|
||||
<div v-else>{{ t('Task Completed', { completed: completed.length, failed: failed.length }) }}</div>
|
||||
</n-button>
|
||||
|
||||
<n-drawer v-model:show="showDrawer" :width="502">
|
||||
<n-drawer-content :title="t('Task Management')" closable>
|
||||
<div class="flex flex-col gap-4">
|
||||
<n-alert type="warning" :show-icon="false">
|
||||
<p><span class="text-red-500">{{ t('Browser Warning') }}</span></p>
|
||||
<p><span class="text-red-500">{{ t('Cache Warning') }}</span></p>
|
||||
</n-alert>
|
||||
<div v-if="total > 0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ total - (pending.length + processing.length) }}/{{ total }}</div>
|
||||
<n-button text type="info" @click="clearTasks">{{ t('Clear Records') }}</n-button>
|
||||
</div>
|
||||
<n-progress type="line" :height="2" :percentage="percentage" :show-indicator="false" :processing="percentage < 100" />
|
||||
</div>
|
||||
<n-tabs type="line" animated v-model:value="tab">
|
||||
<n-tab-pane :tab="t('Pending', { count: pending.length })" name="pending">
|
||||
<slot name="task-list" :tasks="pending" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Processing', { count: processing.length })" name="processing">
|
||||
<slot name="task-list" :tasks="processing" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Completed', { count: completed.length })" name="completed">
|
||||
<slot name="task-list" :tasks="completed" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Failed', { count: failed.length })" name="failed">
|
||||
<slot name="task-list" :tasks="failed" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
tasks: any[]
|
||||
processingStatus: string
|
||||
onClearTasks: () => void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const showDrawer = ref(false)
|
||||
|
||||
const total = computed(() => props.tasks.length)
|
||||
const percentage = computed(() => total.value === 0 ? 100 : Math.floor((completed.value.length / total.value) * 100))
|
||||
|
||||
const pending = computed(() => props.tasks.filter(task => task.status === 'pending'))
|
||||
const processing = computed(() => props.tasks.filter(task => task.status === props.processingStatus))
|
||||
const completed = computed(() => props.tasks.filter(task => task.status === 'completed'))
|
||||
const failed = computed(() => props.tasks.filter(task => task.status === 'failed'))
|
||||
|
||||
const tab = ref('pending')
|
||||
|
||||
// 根据任务执行状态切换任务列表
|
||||
watch(percentage, () => {
|
||||
if (processing.value.length > 0) {
|
||||
tab.value = 'processing'
|
||||
} else {
|
||||
tab.value = 'completed'
|
||||
}
|
||||
})
|
||||
|
||||
// 第一次加入任务时自动打开抽屉
|
||||
const autoOpened = ref(false)
|
||||
watch(total, (newVal, oldVal) => {
|
||||
if (!autoOpened.value && !showDrawer.value && oldVal === 0 && newVal > 0) {
|
||||
showDrawer.value = true
|
||||
autoOpened.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const toggleDrawer = () => {
|
||||
showDrawer.value = !showDrawer.value
|
||||
}
|
||||
|
||||
const clearTasks = () => {
|
||||
props.onClearTasks()
|
||||
}
|
||||
</script>
|
||||
@@ -1,87 +1,18 @@
|
||||
<template>
|
||||
<n-button text v-if="total > 0" @click="toggleDrawer">
|
||||
<div v-if="pending.length" class="flex items-center gap-2">
|
||||
<n-spin :size="14" />
|
||||
<span>{{ t('Task In Progress', { uploading: uploading.length, completed: completed.length }) }}</span>
|
||||
</div>
|
||||
<div v-else>{{ t('Upload Completed', { completed: completed.length, failed: failed.length }) }}</div>
|
||||
</n-button>
|
||||
|
||||
<n-drawer v-model:show="showDrawer" :width="502">
|
||||
<n-drawer-content :title="t('Task Management')" closable>
|
||||
<div class="flex flex-col gap-4">
|
||||
<n-alert type="warning" :show-icon="false">
|
||||
<p>{{ t('Browser Warning') }}</p>
|
||||
<p>{{ t('Cache Warning') }}</p>
|
||||
</n-alert>
|
||||
<div v-if="total > 0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ total - (pending.length + uploading.length) }}/{{ total }}</div>
|
||||
<n-button text type="info" @click="clearTasks">{{ t('Clear Records') }}</n-button>
|
||||
</div>
|
||||
<n-progress type="line" :height="2" :percentage="percentage" :show-indicator="false" :processing="percentage < 100" />
|
||||
</div>
|
||||
<n-tabs type="line" animated v-model:value="tab">
|
||||
<n-tab-pane :tab="t('Pending', { count: pending.length })" name="pending">
|
||||
<object-upload-task-list :tasks="pending" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Uploading', { count: uploading.length })" name="uploading">
|
||||
<object-upload-task-list :tasks="uploading" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Completed', { count: completed.length })" name="completed">
|
||||
<object-upload-task-list :tasks="completed" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane :tab="t('Failed', { count: failed.length })" name="failed">
|
||||
<object-upload-task-list :tasks="failed" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
<object-task-stats :tasks="tasks" processing-status="uploading" :on-clear-tasks="clearTasks">
|
||||
<template #task-list="{ tasks }">
|
||||
<object-upload-task-list :tasks="tasks" />
|
||||
</template>
|
||||
</object-task-stats>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUploadTaskManagerStore } from '~/store/upload-tasks'
|
||||
|
||||
const { t } = useI18n()
|
||||
const showDrawer = ref(false)
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useUploadTaskManagerStore } from '~/store/upload-tasks'
|
||||
|
||||
const store = useUploadTaskManagerStore()
|
||||
const tasks = computed(() => store.tasks)
|
||||
|
||||
const total = computed(() => tasks.value.length)
|
||||
const percentage = computed(() => total.value === 0 ? 100 : Math.floor((completed.value.length / total.value) * 100))
|
||||
|
||||
const pending = computed(() => tasks.value.filter(task => task.status === 'pending'))
|
||||
const uploading = computed(() => tasks.value.filter(task => task.status === 'uploading'))
|
||||
const completed = computed(() => tasks.value.filter(task => task.status === 'completed'))
|
||||
const failed = computed(() => tasks.value.filter(task => task.status === 'failed'))
|
||||
|
||||
const tab = ref('pending')
|
||||
|
||||
// 根据任务执行状态切换任务列表,如果有进行中的任务则默认展示进行中,否则展示已完成
|
||||
watch(percentage, () => {
|
||||
if (uploading.value.length > 0) {
|
||||
tab.value = 'uploading'
|
||||
} else {
|
||||
tab.value = 'completed'
|
||||
}
|
||||
})
|
||||
|
||||
// 第一次加入任务时自动打开抽屉
|
||||
const autoOpened = ref(false)
|
||||
watch(total, (newVal, oldVal) => {
|
||||
if (!autoOpened.value && !showDrawer.value && oldVal === 0 && newVal > 0) {
|
||||
showDrawer.value = true
|
||||
autoOpened.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const toggleDrawer = () => {
|
||||
showDrawer.value = !showDrawer.value
|
||||
}
|
||||
|
||||
const clearTasks = () => {
|
||||
store.clearTasks()
|
||||
}
|
||||
|
||||
@@ -359,7 +359,6 @@
|
||||
"If no versions remain, delete references to this object": "如果没有剩余版本,则删除此对象的引用",
|
||||
"Version Cleanup": "版本清理",
|
||||
"Delete all expired versions": "删除所有过期的版本",
|
||||
"Expiration": "过期",
|
||||
"Transition": "转换",
|
||||
"Add Bucket Replication Rule": "添加存储桶复制规则",
|
||||
"Definitions": "定义",
|
||||
@@ -452,6 +451,7 @@
|
||||
"Search Access Key": "搜索访问密钥",
|
||||
"Add Access Key": "添加访问密钥",
|
||||
"Expiration": "过期时间",
|
||||
"Processing": "进行中",
|
||||
"Disabled": "已禁用",
|
||||
"Description": "描述",
|
||||
"Are you sure you want to delete all selected keys?": "您确定要删除所有选中的密钥吗?",
|
||||
|
||||
+45
-39
@@ -59,29 +59,39 @@ class BucketAccessPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidStatement(statement: Statement, bucketName: string): boolean {
|
||||
if (Array.from(statement.Actions).every(action => !validActions.has(action))) {
|
||||
return false;
|
||||
}
|
||||
// 提取公共的判断逻辑
|
||||
function hasValidPrincipal(statement: Statement): boolean {
|
||||
return statement.Principal.AWS?.has('*') || false;
|
||||
}
|
||||
|
||||
if (statement.Effect !== 'Allow') {
|
||||
return false;
|
||||
}
|
||||
function hasValidEffect(statement: Statement): boolean {
|
||||
return statement.Effect === 'Allow';
|
||||
}
|
||||
|
||||
if (!statement.Principal.AWS || !statement.Principal.AWS.has('*')) {
|
||||
return false;
|
||||
}
|
||||
function hasValidActions(statement: Statement, validActions: Set<string>): boolean {
|
||||
return Array.from(statement.Actions).every(action => validActions.has(action));
|
||||
}
|
||||
|
||||
function hasValidResource(statement: Statement, bucketName: string): boolean {
|
||||
const bucketResource = `${awsResourcePrefix}${bucketName}`;
|
||||
if (statement.Resources.has(bucketResource)) {
|
||||
return true;
|
||||
return statement.Resources.has(bucketResource) ||
|
||||
Array.from(statement.Resources).some(resource => resource.startsWith(bucketResource + '/'));
|
||||
}
|
||||
|
||||
export function isValidStatement(statement: Statement, bucketName: string): boolean {
|
||||
if (!hasValidActions(statement, validActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.from(statement.Resources).some(resource => resource.startsWith(bucketResource + '/'))) {
|
||||
return true;
|
||||
if (!hasValidEffect(statement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!hasValidPrincipal(statement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasValidResource(statement, bucketName);
|
||||
}
|
||||
|
||||
export function newBucketStatement(policy: BucketPolicy, bucketName: string, prefix: string): Statement[] {
|
||||
@@ -259,7 +269,7 @@ export function removeStatements(statements: Statement[], bucketName: string, pr
|
||||
out.push(statement);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (statement.Resources.has(bucketResource)) {
|
||||
@@ -378,22 +388,22 @@ export function appendStatement(statements: Statement[], statement: Statement):
|
||||
function appendStatement(statements: Statement[], statement: Statement): Statement[] {
|
||||
for (let i = 0; i < statements.length; i++) {
|
||||
const s = statements[i];
|
||||
|
||||
|
||||
// 第一个合并条件
|
||||
if (areSetsEqual(s.Actions, statement.Actions) &&
|
||||
s.Effect === statement.Effect &&
|
||||
areSetsEqual(s.Principal.AWS, statement.Principal.AWS) &&
|
||||
deepEqual(s.Conditions, statement.Conditions)) {
|
||||
|
||||
|
||||
statements[i].Resources = new Set([...s.Resources, ...statement.Resources]);
|
||||
return statements;
|
||||
}
|
||||
}
|
||||
// 第二个合并条件
|
||||
else if (areSetsEqual(s.Resources, statement.Resources) &&
|
||||
s.Effect === statement.Effect &&
|
||||
areSetsEqual(s.Principal.AWS, statement.Principal.AWS) &&
|
||||
deepEqual(s.Conditions, statement.Conditions)) {
|
||||
|
||||
|
||||
statements[i].Actions = new Set([...s.Actions, ...statement.Actions]);
|
||||
return statements;
|
||||
}
|
||||
@@ -401,17 +411,17 @@ function appendStatement(statements: Statement[], statement: Statement): Stateme
|
||||
// 处理交集条件
|
||||
const resourceIntersection = setIntersection(s.Resources, statement.Resources);
|
||||
const actionIntersection = setIntersection(s.Actions, statement.Actions);
|
||||
const principalIntersection = setIntersection(s.Principal.AWS as Set<string>, statement.Principal.AWS as Set<string>);
|
||||
|
||||
const principalIntersection = setIntersection(s.Principal.AWS as Set<string>, statement.Principal.AWS as Set<string>);
|
||||
|
||||
if (areSetsEqual(resourceIntersection, statement.Resources) &&
|
||||
areSetsEqual(actionIntersection, statement.Actions) &&
|
||||
s.Effect === statement.Effect &&
|
||||
areSetsEqual(principalIntersection, statement.Principal.AWS)) {
|
||||
|
||||
|
||||
if (deepEqual(s.Conditions, statement.Conditions)) {
|
||||
return statements;
|
||||
}
|
||||
|
||||
|
||||
if (s.Conditions && statement.Conditions) {
|
||||
if (areSetsEqual(s.Resources, statement.Resources)) {
|
||||
statements[i].Conditions = mergeConditionMap(s.Conditions, statement.Conditions);
|
||||
@@ -463,30 +473,26 @@ export function appendStatements(statements: Statement[], appendStatements: Stat
|
||||
for (const s of appendStatements) {
|
||||
statements = appendStatement(statements, s);
|
||||
}
|
||||
console.log("🚀 ~ appendStatements ~ statements:", statements)
|
||||
console.log("🚀 ~ appendStatements ~ statements:", statements)
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
export function getBucketPolicy(statement: Statement, prefix: string): [boolean, boolean, boolean] {
|
||||
if (!(statement.Effect === 'Allow' && statement.Principal.AWS?.has('*'))) {
|
||||
if (!hasValidEffect(statement) || !hasValidPrincipal(statement)) {
|
||||
return [false, false, false];
|
||||
}
|
||||
|
||||
const commonFound = Array.from(statement.Actions).every(action => commonBucketActions.has(action)) && !statement.Conditions;
|
||||
const writeOnly = Array.from(statement.Actions).every(action => writeOnlyBucketActions.has(action)) && !statement.Conditions;
|
||||
const commonFound = hasValidActions(statement, commonBucketActions) && !statement.Conditions;
|
||||
const writeOnly = hasValidActions(statement, writeOnlyBucketActions) && !statement.Conditions;
|
||||
let readOnly = false;
|
||||
|
||||
if (Array.from(statement.Actions).every(action => readOnlyBucketActions.has(action))) {
|
||||
if (hasValidActions(statement, readOnlyBucketActions)) {
|
||||
if (prefix && statement.Conditions) {
|
||||
const stringEqualsValue = statement.Conditions['StringEquals'];
|
||||
const values = stringEqualsValue ? stringEqualsValue['s3:prefix'] || new Set() : new Set();
|
||||
if (values.has(prefix)) {
|
||||
readOnly = true;
|
||||
}
|
||||
} else if (!prefix && !statement.Conditions) {
|
||||
readOnly = true;
|
||||
} else if (prefix && !statement.Conditions) {
|
||||
readOnly = values.has(prefix);
|
||||
} else if (!prefix || !statement.Conditions) {
|
||||
readOnly = true;
|
||||
}
|
||||
}
|
||||
@@ -495,9 +501,9 @@ export function getBucketPolicy(statement: Statement, prefix: string): [boolean,
|
||||
}
|
||||
|
||||
export function getObjectPolicy(statement: Statement): [boolean, boolean] {
|
||||
if (statement.Effect === 'Allow' && statement.Principal.AWS?.has('*') && !statement.Conditions) {
|
||||
const readOnly = Array.from(statement.Actions).every(action => readOnlyObjectActions.has(action));
|
||||
const writeOnly = Array.from(statement.Actions).every(action => writeOnlyObjectActions.has(action));
|
||||
if (hasValidEffect(statement) && hasValidPrincipal(statement) && !statement.Conditions) {
|
||||
const readOnly = hasValidActions(statement, readOnlyObjectActions);
|
||||
const writeOnly = hasValidActions(statement, writeOnlyObjectActions);
|
||||
return [readOnly, writeOnly];
|
||||
}
|
||||
return [false, false];
|
||||
@@ -552,7 +558,7 @@ export function getPolicy(statements: Statement[], bucketName: string, prefix: s
|
||||
}
|
||||
|
||||
let policy: BucketPolicy = BucketPolicyConstants.None;
|
||||
console.log("🚀 ~ getPolicy ~ bucketCommonFound:", bucketCommonFound)
|
||||
console.log("🚀 ~ getPolicy ~ bucketCommonFound:", bucketCommonFound)
|
||||
|
||||
if (bucketCommonFound) {
|
||||
if (bucketReadOnly && bucketWriteOnly && objReadOnly && objWriteOnly) {
|
||||
|
||||
Reference in New Issue
Block a user