index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <template>
  2. <div>
  3. <a-alert class="mb-2" :message="$t('iam.policy.definition.hint', [$t('dictionary.policy'),$t('dictionary.role')])" type="success" />
  4. <a-form-model
  5. ref="form"
  6. :model="model"
  7. :rules="rules"
  8. v-bind="formItemLayout">
  9. <a-form-model-item :label="$t('dictionary.domain')" v-if="!isUpdate && isAdminMode && l3PermissionEnable" prop="domain">
  10. <base-select
  11. v-model="model.domain"
  12. resource="domains"
  13. version="v1"
  14. remote
  15. :params="{ enabled: true }"
  16. :remote-fn="q => ({ filter: `name.contains(${q})` })"
  17. :select-props="{ allowClear: true }"
  18. @update:item="handleDomainChange" />
  19. </a-form-model-item>
  20. <a-form-model-item :label="$t('system.text_101')" prop="name">
  21. <a-input v-model="model.name" :placeholder="$t('system.text_323', [$t('dictionary.policy')])" />
  22. </a-form-model-item>
  23. <a-form-model-item :label="$t('common.description')" prop="description">
  24. <a-input v-model="model.description" :placeholder="$t('common.tips.input', [$t('common.description')])" />
  25. </a-form-model-item>
  26. <a-form-model-item :label="$t('system.text_326', [$t('dictionary.policy')])" porp="scope">
  27. <template v-if="!isUpdate">
  28. <scope-select v-model="model.scope" />
  29. </template>
  30. <template v-else>{{ $t(`policyScopeLabel.${model.scope}`) }}</template>
  31. </a-form-model-item>
  32. <a-form-model-item :label="$t('iam.policy.editor.title')">
  33. <a-radio-group :default-value="editType" :value="editType" @change="e => $emit('edit-type-change', e.target.value)">
  34. <template v-for="item of editTypeOptions">
  35. <a-radio-button :value="item.key" :key="item.key">{{ item.label }}</a-radio-button>
  36. </template>
  37. </a-radio-group>
  38. </a-form-model-item>
  39. <a-form-model-item :label="$t('system.text_327', [$t('dictionary.policy')])">
  40. <code-mirror v-model="yamlPolicy" :options="cmOptions" />
  41. </a-form-model-item>
  42. </a-form-model>
  43. </div>
  44. </template>
  45. <script>
  46. import 'codemirror/theme/material.css'
  47. import yaml from 'js-yaml'
  48. import * as R from 'ramda'
  49. import { mapGetters } from 'vuex'
  50. import { SCOPES_MAP } from '@/constants'
  51. import i18n from '@/locales'
  52. import { getPolicyResCheckedList } from '@/utils/policy/policy-res-list'
  53. import { POLICY_WHITE_LIST } from '@/constants/policy'
  54. import { genPolicyGroups } from '../../utils'
  55. import { DEFAULT_ACTIONS_KEY } from '../../constants'
  56. import ScopeSelect from './ScopeSelect'
  57. // 权限级别
  58. const policyLevel = {
  59. deny: 0,
  60. allow: 1,
  61. }
  62. const genPolicyRuleOptions = (scopeResource, isUpdate, permissions) => {
  63. const groups = genPolicyGroups()
  64. let checkAllDisabled = true
  65. const options = groups.map(item => {
  66. let serviceDisable = true
  67. const resources = item.resources.map(resource => {
  68. let resourceDisabled = true
  69. // ---------------- actions start map ----------------
  70. const actions = DEFAULT_ACTIONS_KEY.map(action => {
  71. let actionDisabled = true
  72. let operatorPolicy = 'allow'
  73. let currentPolicy = 'allow'
  74. if (isUpdate) {
  75. const permissionItem = permissions[`${resource.resource}_${action}`]
  76. // 操作者权限
  77. operatorPolicy = permissionItem[permissionItem.length - 1]
  78. // 当前权限
  79. currentPolicy = permissionItem[permissionItem.length - 2]
  80. // 当前权限大于操作权限属于逾越权限,需要disabled
  81. if (
  82. operatorPolicy === 'allow' &&
  83. policyLevel[currentPolicy] <= policyLevel[operatorPolicy]
  84. ) {
  85. actionDisabled = false
  86. }
  87. } else {
  88. actionDisabled = false
  89. }
  90. if (!actionDisabled) {
  91. resourceDisabled = false
  92. }
  93. return {
  94. label: i18n.t(`policyDefaultActions.${action}`),
  95. action,
  96. value: action,
  97. service: item.service,
  98. resource: resource.resource,
  99. disabled: actionDisabled,
  100. operatorPolicy,
  101. currentPolicy,
  102. }
  103. })
  104. // ---------------- actions map end ----------------
  105. // ---------------- extras actions start map ----------------
  106. if (resource.extras && resource.extras) {
  107. resource.extras.forEach(extras => {
  108. let actionDisabled = true
  109. let operatorPolicy = 'allow'
  110. let currentPolicy = 'allow'
  111. if (isUpdate) {
  112. const permissionItem = permissions[`${resource.resource}_${extras.action}_${extras.value}`]
  113. // 操作者权限
  114. operatorPolicy = permissionItem[permissionItem.length - 1]
  115. // 当前权限
  116. currentPolicy = permissionItem[permissionItem.length - 2]
  117. // 当前权限大于操作权限属于逾越权限,需要disabled
  118. if (
  119. operatorPolicy === 'allow' &&
  120. policyLevel[currentPolicy] <= policyLevel[operatorPolicy]
  121. ) {
  122. actionDisabled = false
  123. }
  124. } else {
  125. actionDisabled = false
  126. }
  127. if (!actionDisabled) {
  128. resourceDisabled = false
  129. }
  130. actions.push({
  131. parent: extras.action,
  132. extraAction: extras.value,
  133. label: extras.label,
  134. action: `${extras.action}_${extras.value}`,
  135. value: `${extras.action}_${extras.value}`,
  136. service: item.service,
  137. resource: resource.resource,
  138. disabled: actionDisabled,
  139. operatorPolicy,
  140. currentPolicy,
  141. })
  142. })
  143. }
  144. // ---------------- extras actions map end ----------------
  145. if (!resourceDisabled) {
  146. serviceDisable = false
  147. }
  148. return {
  149. checkAll: false,
  150. service: item.service,
  151. resource: resource.resource,
  152. label: resource.label,
  153. checked: [],
  154. isDomainRes: scopeResource.domain.includes(resource.resource),
  155. isSystemRes: scopeResource.system.includes(resource.resource),
  156. isIndeterminate: false,
  157. actions,
  158. disabled: resourceDisabled,
  159. }
  160. })
  161. if (!serviceDisable) {
  162. checkAllDisabled = false
  163. }
  164. return {
  165. checkAll: false,
  166. label: item.label,
  167. service: item.service,
  168. isIndeterminate: false,
  169. resources,
  170. disabled: serviceDisable,
  171. }
  172. })
  173. return { options, checkAllDisabled }
  174. }
  175. export default {
  176. name: 'PolicyForm',
  177. components: {
  178. ScopeSelect,
  179. },
  180. props: {
  181. policy: Object,
  182. permissions: Object,
  183. isUpdate: Boolean,
  184. editType: {
  185. type: String,
  186. required: true,
  187. },
  188. policyLoading: Boolean,
  189. },
  190. data () {
  191. const initialNameValue = (this.policy && this.policy.name) || ''
  192. const initialDomainValue = (this.policy && this.policy.domain_id) || this.$store.getters.userInfo.projectDomainId
  193. const initialScopeValue = (this.policy && this.policy.scope) || SCOPES_MAP.project.key
  194. const initialYamlPolicyValue = (this.editType === 'yaml' && this.policy && this.policy.policy) || 'policy:\n "*": allow'
  195. const initialCheckboxPolicyValue = (this.policy && this.policy.policy) || {}
  196. const initialDescriptionValue = (this.policy && this.policy.description) || ''
  197. return {
  198. checkAllDisabled: false,
  199. scopesMap: SCOPES_MAP,
  200. policyRuleOptions: [],
  201. model: {
  202. name: initialNameValue,
  203. scope: initialScopeValue,
  204. domain: initialDomainValue,
  205. description: initialDescriptionValue,
  206. },
  207. currentDomain: {},
  208. rules: {
  209. name: [
  210. { required: true, message: this.$t('common.text00042') },
  211. ],
  212. },
  213. formItemLayout: {
  214. wrapperCol: {
  215. span: 21,
  216. },
  217. labelCol: {
  218. span: 3,
  219. },
  220. },
  221. editTypeOptions: [
  222. { key: 'yaml', label: this.$t('system.policy_edit_type_yaml') },
  223. ],
  224. yamlPolicy: initialYamlPolicyValue,
  225. checkboxPolicy: initialCheckboxPolicyValue,
  226. cmOptions: {
  227. tabSize: 2,
  228. styleActiveLine: true,
  229. lineNumbers: true,
  230. line: true,
  231. theme: 'material',
  232. mode: 'text/x-yaml',
  233. },
  234. showPolicyCheckbox: this.editType === 'checkbox',
  235. policyResCheckedList: getPolicyResCheckedList(this.policy?.policy),
  236. }
  237. },
  238. computed: {
  239. ...mapGetters(['l3PermissionEnable', 'isAdminMode', 'scope', 'scopeResource']),
  240. roleParams () {
  241. const params = {
  242. details: true,
  243. scope: this.scope,
  244. }
  245. if (this.isAdminMode && this.model.domain) {
  246. params.domain_id = this.model.domain
  247. }
  248. return params
  249. },
  250. orgParams () {
  251. const params = {
  252. scope: this.scope,
  253. }
  254. if (this.isAdminMode && this.model.domain) {
  255. params.domain_id = this.model.domain
  256. }
  257. return params
  258. },
  259. },
  260. watch: {
  261. policy (val) {
  262. if (!R.isNil(val) && !R.isEmpty(val)) {
  263. const name = (val && val.name) || ''
  264. const domain = (val && val.domain_id) || ''
  265. const scope = (val && val.scope) || SCOPES_MAP.project.key
  266. const description = (val && val.policy.description) || ''
  267. this.yamlPolicy = (this.editType === 'yaml' && val.policy) || ''
  268. this.model.name = name
  269. this.model.scope = scope
  270. this.model.domain = domain
  271. this.model.description = description
  272. }
  273. },
  274. editType (val) {
  275. if (val === 'checkbox') {
  276. this.$nextTick(() => {
  277. setTimeout(() => {
  278. this.showPolicyCheckbox = true
  279. }, 100)
  280. })
  281. } else {
  282. this.showPolicyCheckbox = false
  283. }
  284. },
  285. },
  286. created () {
  287. const { options, checkAllDisabled } = genPolicyRuleOptions(this.scopeResource, this.isUpdate, this.permissions)
  288. this.policyRuleOptions = options
  289. this.checkAllDisabled = checkAllDisabled
  290. },
  291. methods: {
  292. async getData () {
  293. try {
  294. await this.$refs.form.validate()
  295. const { name, scope, domain, description } = this.model
  296. // const { project_tags, objectTagsArray, domainTagsArray } = this
  297. let data = {}
  298. let policy
  299. if (this.editType === 'checkbox') {
  300. policy = this.genPostPolicyData()
  301. // 无论如何设置,usages都设置为有权限
  302. // policy.compute.usages = {
  303. // get: { '*': 'allow' },
  304. // list: { '*': 'allow' },
  305. // }
  306. // policy.image.usages = {
  307. // get: { '*': 'allow' },
  308. // list: { '*': 'allow' },
  309. // }
  310. // policy.identity.usages = {
  311. // get: { '*': 'allow' },
  312. // list: { '*': 'allow' },
  313. // }
  314. // 自定义处理
  315. this.customPolicy(policy)
  316. data = {
  317. type: name,
  318. policy: {
  319. policy,
  320. },
  321. scope,
  322. }
  323. } else if (this.editType === 'yaml') {
  324. data.type = name
  325. data.name = name
  326. data.policy = yaml.safeLoad(this.yamlPolicy)
  327. data.scope = scope
  328. }
  329. if (this.isAdminMode) {
  330. data.domain_id = domain
  331. }
  332. if (description) {
  333. data.description = description
  334. }
  335. return data
  336. } catch (error) {
  337. throw error
  338. }
  339. },
  340. genPostPolicyData () {
  341. const ret = {}
  342. for (let i = 0, len = this.policyRuleOptions.length; i < len; i++) {
  343. const item = this.policyRuleOptions[i]
  344. const service = item.service
  345. ret[service] = {}
  346. for (let j = 0, jlen = item.resources.length; j < jlen; j++) {
  347. const resource = item.resources[j]
  348. let isContinue = true
  349. if (this.model.scope === SCOPES_MAP.project.key) {
  350. if (resource.isDomainRes || resource.isSystemRes) isContinue = false
  351. }
  352. if (this.model.scope === SCOPES_MAP.domain.key) {
  353. if (resource.isSystemRes) isContinue = false
  354. }
  355. if (!isContinue) continue
  356. const resKey = resource.resource
  357. ret[service][resKey] = {}
  358. const policyCheckedActions = this.getPolicyActionsByServiceAndReskey(service, resKey)
  359. for (let k = 0, klen = DEFAULT_ACTIONS_KEY.length; k < klen; k++) {
  360. const action = DEFAULT_ACTIONS_KEY[k]
  361. if (resource.checked.includes(action) || policyCheckedActions.includes(action)) {
  362. ret[service][resKey][action] = {
  363. '*': 'allow',
  364. }
  365. } else {
  366. ret[service][resKey][action] = {
  367. '*': 'deny',
  368. }
  369. }
  370. /**
  371. * 如果大于DEFAULT_ACTIONS_KEY的长度
  372. * 则代表有其他的 extras, 如 get vnc
  373. * 则过滤出来当前 action 的 extras,然后进行重新组装该action的policy
  374. */
  375. if (resource.actions.length > DEFAULT_ACTIONS_KEY.length) {
  376. const extras = resource.actions.slice(DEFAULT_ACTIONS_KEY.length).filter(extra => extra.parent === action)
  377. if (extras && extras.length > 0) {
  378. for (let m = 0, mlen = extras.length; m < mlen; m++) {
  379. const extra = extras[m]
  380. const checkAction = ((extra) => {
  381. let action = extra.action
  382. if (extra.parent === 'perform') {
  383. action = extra.extraAction === '*' ? extra.parent : extra.extraAction
  384. } else {
  385. action = extra.parent || extra.action
  386. }
  387. return action
  388. })(extra)
  389. if (resource.checked.includes(extra.action) || policyCheckedActions.includes(checkAction)) {
  390. ret[service][resKey][action][extra.extraAction] = 'allow'
  391. } else {
  392. ret[service][resKey][action][extra.extraAction] = 'deny'
  393. }
  394. }
  395. }
  396. }
  397. }
  398. }
  399. }
  400. return ret
  401. },
  402. handleDomainChange (domain) {
  403. this.currentDomain = domain
  404. },
  405. customPolicy (policy) {
  406. // 勾选迁移权限后就同时支持冷、热迁移
  407. const migrate = policy.compute.servers.perform.migrate
  408. if (migrate) {
  409. policy.compute.servers.perform['live-migrate'] = migrate
  410. }
  411. const cpuset = policy.compute.servers.perform.cpuset
  412. if (cpuset) {
  413. policy.compute.servers.get['cpuset-cores'] = cpuset
  414. }
  415. },
  416. checkMenuOptionsChange (v) {
  417. this.policyResCheckedList = v
  418. },
  419. getPolicyActionsByServiceAndReskey (service, resKey) {
  420. const policyResCheckedList = Object.values(this.policyResCheckedList)
  421. const currentRes = policyResCheckedList.find(item => item.service === service && item.resource === resKey)
  422. const permisArr = [...POLICY_WHITE_LIST]
  423. if (currentRes) {
  424. currentRes.options.forEach(item => {
  425. permisArr.push(item)
  426. })
  427. }
  428. return Array.from(new Set(permisArr))
  429. },
  430. },
  431. }
  432. </script>