Add ability to manage and grant IAM permissions as policy
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
5645e8fae1
commit
f2995899ec
@ -28,7 +28,7 @@ const template = `
|
||||
v-if="field.type === 'display' && (Array.isArray(field.hidden) ? !field.hidden.includes(mode) : !field.hidden) && (typeof field.if !== 'function' || field.if(data))"
|
||||
v-html="typeof field.display === 'function' ? field.display(data) : field.display"
|
||||
></span>
|
||||
<span v-if="field.type.startsWith('select') && (Array.isArray(field.hidden) ? !field.hidden.includes(mode) : !field.hidden) && (typeof field.if !== 'function' || field.if(data))">
|
||||
<span v-if="field.type.startsWith('select') && (Array.isArray(field.hidden) ? !field.hidden.includes(mode) : !field.hidden) && (typeof field.if !== 'function' || field.if(data, field.options))">
|
||||
<label :for="uuid+field.field">{{ field.name }}</label>
|
||||
<select
|
||||
:id="uuid+field.field"
|
||||
|
@ -60,6 +60,12 @@ export default class SideBarComponent extends Component {
|
||||
type: 'resource',
|
||||
resource: 'iam/Policy',
|
||||
},
|
||||
{
|
||||
text: 'IAM Permissions',
|
||||
action: 'list',
|
||||
type: 'resource',
|
||||
resource: 'iam/Permission',
|
||||
},
|
||||
{
|
||||
text: 'Computers',
|
||||
action: 'list',
|
||||
|
87
app/assets/app/resource/iam/Permission.resource.js
Normal file
87
app/assets/app/resource/iam/Permission.resource.js
Normal file
@ -0,0 +1,87 @@
|
||||
import CRUDBase from '../CRUDBase.js'
|
||||
|
||||
class PermissionResource extends CRUDBase {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.endpoint = '/api/v1/iam/permission'
|
||||
this.required_fields = ['target_type', 'permission']
|
||||
this.permission_base = 'v1:iam:permission'
|
||||
|
||||
this.item = 'IAM Permission'
|
||||
this.plural = 'IAM Permissions'
|
||||
|
||||
this.listing_definition = {
|
||||
display: `Permissions are custom actions that can be performed on a given IAM target by the subject.`,
|
||||
columns: [
|
||||
{
|
||||
name: 'Target Type',
|
||||
field: 'target_type',
|
||||
renderer: type => type.split('_').map(x => `${x.charAt(0).toUpperCase()}${x.slice(1)}`).join(' '),
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'main',
|
||||
action: 'insert',
|
||||
text: 'Create New',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'row',
|
||||
action: 'update',
|
||||
icon: 'fa fa-edit',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
type: 'resource',
|
||||
position: 'row',
|
||||
action: 'delete',
|
||||
icon: 'fa fa-times',
|
||||
color: 'danger',
|
||||
confirm: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
this.form_definition = {
|
||||
fields: [
|
||||
{
|
||||
name: 'Target Type',
|
||||
field: 'target_type',
|
||||
required: true,
|
||||
type: 'select',
|
||||
options: [
|
||||
{display: 'Application', value: 'application'},
|
||||
{display: 'Api Scope', value: 'api_scope'},
|
||||
{display: 'Machine', value: 'machine'},
|
||||
{display: 'Machine Group', value: 'machine_group'},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
/*handlers: {
|
||||
insert: {
|
||||
action: 'back',
|
||||
},
|
||||
update: {
|
||||
action: 'back',
|
||||
},
|
||||
},*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const iam_permission = new PermissionResource()
|
||||
export { iam_permission }
|
@ -41,6 +41,11 @@ class PolicyResource extends CRUDBase {
|
||||
name: 'Target',
|
||||
field: 'target_display',
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
renderer: permission => permission || '-',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
@ -174,6 +179,70 @@ class PolicyResource extends CRUDBase {
|
||||
},
|
||||
if: (form_data) => form_data.target_type === 'machine_group'
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
required: false,
|
||||
type: 'select.dynamic',
|
||||
options: {
|
||||
resource: 'iam/Permission',
|
||||
display: 'permission',
|
||||
value: 'permission',
|
||||
other_params: {
|
||||
target_type: 'application',
|
||||
include_unset: true,
|
||||
},
|
||||
},
|
||||
if: (form_data, opts) => form_data.target_type === 'application' && opts?.length
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
required: false,
|
||||
type: 'select.dynamic',
|
||||
options: {
|
||||
resource: 'iam/Permission',
|
||||
display: 'permission',
|
||||
value: 'permission',
|
||||
other_params: {
|
||||
target_type: 'api_scope',
|
||||
include_unset: true,
|
||||
},
|
||||
},
|
||||
if: (form_data, opts) => form_data.target_type === 'api_scope' && opts?.length
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
required: false,
|
||||
type: 'select.dynamic',
|
||||
options: {
|
||||
resource: 'iam/Permission',
|
||||
display: 'permission',
|
||||
value: 'permission',
|
||||
other_params: {
|
||||
target_type: 'machine',
|
||||
include_unset: true,
|
||||
},
|
||||
},
|
||||
if: (form_data, opts) => form_data.target_type === 'machine' && opts?.length
|
||||
},
|
||||
{
|
||||
name: 'Permission',
|
||||
field: 'permission',
|
||||
required: false,
|
||||
type: 'select.dynamic',
|
||||
options: {
|
||||
resource: 'iam/Permission',
|
||||
display: 'permission',
|
||||
value: 'permission',
|
||||
other_params: {
|
||||
target_type: 'machine_group',
|
||||
include_unset: true,
|
||||
},
|
||||
},
|
||||
if: (form_data, opts) => form_data.target_type === 'machine_group' && opts?.length
|
||||
},
|
||||
],
|
||||
/*handlers: {
|
||||
insert: {
|
||||
|
@ -13,7 +13,7 @@ class IAMController extends Controller {
|
||||
.message(`${req.T('api.missing_field', true)} entity_id, target_id`)
|
||||
.api()
|
||||
|
||||
return res.api(await Policy.check_entity_access(req.body.entity_id, req.body.target_id))
|
||||
return res.api(await Policy.check_entity_access(req.body.entity_id, req.body.target_id, req.body.permission || undefined))
|
||||
}
|
||||
|
||||
async check_user_access(req, res, next) {
|
||||
@ -39,7 +39,7 @@ class IAMController extends Controller {
|
||||
.message(req.T('api.insufficient_permissions'))
|
||||
.api()
|
||||
|
||||
return res.api(await Policy.check_user_access(user, req.body.target_id))
|
||||
return res.api(await Policy.check_user_access(user, req.body.target_id, req.body.permission || undefined))
|
||||
}
|
||||
|
||||
async get_policies(req, res, next) {
|
||||
@ -56,6 +56,33 @@ class IAMController extends Controller {
|
||||
return res.api(data)
|
||||
}
|
||||
|
||||
async get_permissions(req, res, next) {
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
const permissions = await Permission.find({
|
||||
active: true,
|
||||
...(req.query.target_type ? {
|
||||
target_type: req.query.target_type,
|
||||
} : {})
|
||||
})
|
||||
const data = []
|
||||
|
||||
for ( const perm of permissions ) {
|
||||
if ( req.user.can(`iam:permission:${perm.target_type}:view`) ) {
|
||||
data.push(await perm.to_api())
|
||||
}
|
||||
}
|
||||
|
||||
if ( req.query.include_unset ) {
|
||||
data.reverse().push({
|
||||
permission: '',
|
||||
})
|
||||
|
||||
data.reverse()
|
||||
}
|
||||
|
||||
return res.api(data)
|
||||
}
|
||||
|
||||
async get_policy(req, res, next) {
|
||||
const Policy = this.models.get('iam:Policy')
|
||||
const policy = await Policy.findById(req.params.id)
|
||||
@ -73,6 +100,23 @@ class IAMController extends Controller {
|
||||
return res.api(await policy.to_api())
|
||||
}
|
||||
|
||||
async get_permission(req, res, next) {
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
const permission = await Permission.findById(req.params.id)
|
||||
|
||||
if ( !permission )
|
||||
return res.status(404)
|
||||
.message(req.T('iam.permission_not_found'))
|
||||
.api()
|
||||
|
||||
if ( !req.user.can(`iam:permission:${permission.target_type}:view`) )
|
||||
return res.status(401)
|
||||
.message(req.T('api.insufficient_permissions'))
|
||||
.api()
|
||||
|
||||
return res.api(await permission.to_api())
|
||||
}
|
||||
|
||||
async create_policy(req, res, next) {
|
||||
const Policy = this.models.get('iam:Policy')
|
||||
|
||||
@ -154,12 +198,71 @@ class IAMController extends Controller {
|
||||
target_id: req.body.target_id,
|
||||
})
|
||||
|
||||
if ( req.body.permission ) {
|
||||
// Validate the permission and set it, if it is valid
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
const permission = await Permission.findOne({
|
||||
active: true,
|
||||
target_type: req.body.target_type,
|
||||
permission: req.body.permission,
|
||||
})
|
||||
|
||||
if ( permission ) {
|
||||
policy.for_permission = true
|
||||
policy.permission = req.body.permission
|
||||
}
|
||||
}
|
||||
|
||||
await policy.save()
|
||||
req.user.allow(`iam:policy:${policy.id}`)
|
||||
await req.user.save()
|
||||
return res.api(await policy.to_api())
|
||||
}
|
||||
|
||||
async create_permission(req, res, next) {
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
|
||||
const required_fields = ['target_type', 'permission']
|
||||
for ( const field of required_fields ) {
|
||||
if ( !req.body[field] )
|
||||
return res.status(400)
|
||||
.message(`${req.T('api.missing_field')} ${field}`)
|
||||
.api()
|
||||
}
|
||||
|
||||
const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group']
|
||||
if ( !valid_target_types.includes(req.body.target_type) ) {
|
||||
return res.status(400)
|
||||
.message(`${req.T('api.invalid_target_type')}`)
|
||||
.api()
|
||||
}
|
||||
|
||||
if ( !req.user.can(`iam:permission${req.body.target_type}:create`) ) {
|
||||
return res.status(401).api()
|
||||
}
|
||||
|
||||
// Make sure one doesn't already exist
|
||||
const existing = await Permission.findOne({
|
||||
active: true,
|
||||
target_type: req.body.target_type,
|
||||
permission: req.body.permission,
|
||||
})
|
||||
|
||||
if ( existing ) {
|
||||
return res.status(400)
|
||||
.message(req.T('api.permission_already_exists'))
|
||||
.api()
|
||||
}
|
||||
|
||||
const perm = new Permission({
|
||||
target_type: req.body.target_type,
|
||||
permission: req.body.permission,
|
||||
})
|
||||
|
||||
await perm.save()
|
||||
return res.api(await perm.to_api())
|
||||
}
|
||||
|
||||
async update_policy(req, res, next) {
|
||||
const Policy = this.models.get('iam:Policy')
|
||||
const policy = await Policy.findById(req.params.id)
|
||||
@ -249,10 +352,69 @@ class IAMController extends Controller {
|
||||
policy.access_type = req.body.access_type
|
||||
policy.target_type = req.body.target_type
|
||||
policy.target_id = req.body.target_id
|
||||
|
||||
if ( req.body.permission ) {
|
||||
// Validate the permission and set it, if it is valid
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
const permission = await Permission.findOne({
|
||||
active: true,
|
||||
target_type: req.body.target_type,
|
||||
permission: req.body.permission,
|
||||
})
|
||||
|
||||
if ( permission ) {
|
||||
policy.for_permission = true
|
||||
policy.permission = req.body.permission
|
||||
} else {
|
||||
policy.for_permission = false
|
||||
policy.permission = undefined
|
||||
}
|
||||
} else {
|
||||
policy.for_permission = false
|
||||
policy.permission = undefined
|
||||
}
|
||||
|
||||
await policy.save()
|
||||
return res.api()
|
||||
}
|
||||
|
||||
async update_permission(req, res, next) {
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
|
||||
const required_fields = ['target_type', 'permission']
|
||||
for ( const field of required_fields ) {
|
||||
if ( !req.body[field] )
|
||||
return res.status(400)
|
||||
.message(`${req.T('api.missing_field')} ${field}`)
|
||||
.api()
|
||||
}
|
||||
|
||||
const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group']
|
||||
if ( !valid_target_types.includes(req.body.target_type) ) {
|
||||
return res.status(400)
|
||||
.message(`${req.T('api.invalid_target_type')}`)
|
||||
.api()
|
||||
}
|
||||
|
||||
if ( !req.user.can(`iam:permission${req.body.target_type}:update`) ) {
|
||||
return res.status(401).api()
|
||||
}
|
||||
|
||||
// Make sure one doesn't already exist
|
||||
const existing = await Permission.findById(req.params.id)
|
||||
if ( !existing?.active ) {
|
||||
return res.status(404)
|
||||
.message(req.T('api.permission_not_found'))
|
||||
.api()
|
||||
}
|
||||
|
||||
existing.target_type = req.body.target_type
|
||||
existing.permission = req.body.permission
|
||||
|
||||
await existing.save()
|
||||
return res.api(await existing.to_api())
|
||||
}
|
||||
|
||||
async delete_policy(req, res, next) {
|
||||
const Policy = this.models.get('iam:Policy')
|
||||
const policy = await Policy.findById(req.params.id)
|
||||
@ -271,6 +433,27 @@ class IAMController extends Controller {
|
||||
await policy.save()
|
||||
return res.api()
|
||||
}
|
||||
|
||||
async delete_permission(req, res, next) {
|
||||
const Permission = this.models.get('iam:Permission')
|
||||
const permission = await Permission.findById(req.params.id)
|
||||
|
||||
if ( !permission?.active ) {
|
||||
return res.status(404)
|
||||
.message(req.T('api.permission_not_found'))
|
||||
.api()
|
||||
}
|
||||
|
||||
if ( !req.user.can(`iam:permission:${permission.target_type}:delete`) ) {
|
||||
return res.status(401)
|
||||
.message(req.T('api.insufficient_permissions'))
|
||||
.api()
|
||||
}
|
||||
|
||||
permission.active = false
|
||||
await permission.save()
|
||||
return res.api()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = IAMController
|
||||
|
23
app/models/iam/Permission.model.js
Normal file
23
app/models/iam/Permission.model.js
Normal file
@ -0,0 +1,23 @@
|
||||
const { Model } = require('flitter-orm')
|
||||
|
||||
class PermissionModel extends Model {
|
||||
static get schema() {
|
||||
return {
|
||||
active: { type: Boolean, default: true },
|
||||
target_type: String,
|
||||
permission: String
|
||||
}
|
||||
}
|
||||
|
||||
async to_api() {
|
||||
return {
|
||||
_id: this.id,
|
||||
id: this.id,
|
||||
active: this.active,
|
||||
target_type: this.target_type,
|
||||
permission: this.permission,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = PermissionModel
|
@ -15,36 +15,46 @@ class PolicyModel extends Model {
|
||||
target_type: { type: String, default: 'application' }, // application | api_scope | machine | machine_group
|
||||
target_id: String,
|
||||
active: { type: Boolean, default: true },
|
||||
for_permission: { type: Boolean, default: false },
|
||||
permission: String,
|
||||
}
|
||||
}
|
||||
|
||||
static async check_allow(entity_id, target_id) {
|
||||
static async check_allow(entity_id, target_id, permission = undefined) {
|
||||
const policies = await this.find({
|
||||
entity_id,
|
||||
target_id,
|
||||
access_type: 'allow',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
return policies.length > 0
|
||||
}
|
||||
|
||||
static async check_deny(entity_id, target_id) {
|
||||
static async check_deny(entity_id, target_id, permission = undefined) {
|
||||
const policies = await this.find({
|
||||
entity_id,
|
||||
target_id,
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
return policies.length === 0
|
||||
}
|
||||
|
||||
static async check_entity_access(entity_id, target_id) {
|
||||
return (await this.check_allow(entity_id, target_id)) && !(await this.check_deny(entity_id, target_id))
|
||||
static async check_entity_access(entity_id, target_id, permission = undefined) {
|
||||
return (await this.check_allow(entity_id, target_id, permission)) && !(await this.check_deny(entity_id, target_id, permission))
|
||||
}
|
||||
|
||||
static async check_user_denied(user, target_id) {
|
||||
static async check_user_denied(user, target_id, permission = undefined) {
|
||||
const groups = await user.groups()
|
||||
const group_ids = groups.map(x => x.id)
|
||||
|
||||
@ -53,6 +63,10 @@ class PolicyModel extends Model {
|
||||
target_id,
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
const group_denials = await this.find({
|
||||
@ -60,6 +74,10 @@ class PolicyModel extends Model {
|
||||
target_id,
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
return user_denials.length > 0 || group_denials.length > 0
|
||||
@ -95,7 +113,7 @@ class PolicyModel extends Model {
|
||||
return all
|
||||
}
|
||||
|
||||
static async check_user_access(user, target_id) {
|
||||
static async check_user_access(user, target_id, permission = undefined) {
|
||||
const groups = await user.groups()
|
||||
const group_ids = groups.map(x => x.id)
|
||||
const target_ids = await this.get_all_related(target_id)
|
||||
@ -105,6 +123,10 @@ class PolicyModel extends Model {
|
||||
target_id: { $in: target_ids },
|
||||
access_type: 'allow',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
const user_denials = await this.find({
|
||||
@ -112,6 +134,10 @@ class PolicyModel extends Model {
|
||||
target_id: { $in: target_ids },
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
const group_approvals = await this.find({
|
||||
@ -119,6 +145,10 @@ class PolicyModel extends Model {
|
||||
target_id: { $in: target_ids },
|
||||
access_type: 'allow',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
const group_denials = await this.find({
|
||||
@ -126,6 +156,10 @@ class PolicyModel extends Model {
|
||||
target_id: { $in: target_ids },
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
...(permission ? {
|
||||
for_permission: true,
|
||||
permission,
|
||||
} : {})
|
||||
})
|
||||
|
||||
// IF user has explicit denial, deny
|
||||
@ -186,6 +220,8 @@ class PolicyModel extends Model {
|
||||
target_display,
|
||||
target_type: this.target_type,
|
||||
target_id: this.target_id,
|
||||
for_permission: this.for_permission,
|
||||
permission: this.permission,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,14 @@ const iam_routes = {
|
||||
['middleware::api:Permission', { check: 'v1:iam:policy:get' }],
|
||||
'controller::api:v1:IAM.get_policy',
|
||||
],
|
||||
'/permission': [
|
||||
['middleware::api:Permission', { check: 'v1:iam:permission:list' }],
|
||||
'controller::api:v1:IAM.get_permissions',
|
||||
],
|
||||
'/permission/:id': [
|
||||
['middleware::api:Permission', { check: 'v1:iam:permission:get' }],
|
||||
'controller::api:v1:IAM.get_permission',
|
||||
],
|
||||
},
|
||||
|
||||
post: {
|
||||
@ -21,6 +29,10 @@ const iam_routes = {
|
||||
['middleware::api:Permission', { check: 'v1:iam:policy:create' }],
|
||||
'controller::api:v1:IAM.create_policy',
|
||||
],
|
||||
'/permission': [
|
||||
['middleware::api:Permission', { check: 'v1:iam:permission:create' }],
|
||||
'controller::api:v1:IAM.create_permission',
|
||||
],
|
||||
'/check_entity_access': [
|
||||
['middleware::api:Permission', { check: 'v1:iam:check_entity_access' }],
|
||||
'controller::api:v1:IAM.check_entity_access',
|
||||
@ -36,6 +48,10 @@ const iam_routes = {
|
||||
['middleware::api:Permission', { check: 'v1:iam:policy:update' }],
|
||||
'controller::api:v1:IAM.update_policy',
|
||||
],
|
||||
'/permission/:id': [
|
||||
['middleware::api:Permission', { check: 'v1:iam:permission:update' }],
|
||||
'controller::api:v1:IAM.update_permission',
|
||||
],
|
||||
},
|
||||
|
||||
delete: {
|
||||
@ -43,6 +59,10 @@ const iam_routes = {
|
||||
['middleware::api:Permission', { check: 'v1:iam:policy:delete' }],
|
||||
'controller::api:v1:IAM.delete_policy',
|
||||
],
|
||||
'/permission/:id': [
|
||||
['middleware::api:Permission', { check: 'v1:iam:permission:delete' }],
|
||||
'controller::api:v1:IAM.delete_permission',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@ module.exports = exports = {
|
||||
token_not_found: 'Token not found with that ID, or the token has expired.',
|
||||
|
||||
provider_already_exists: 'A service provider with that entity_id already exists.',
|
||||
permission_already_exists: 'A permission for that target_type already exists.',
|
||||
permission_not_found: 'Permission not found with that ID.',
|
||||
|
||||
setting_not_found: 'No such setting exists with that key.',
|
||||
|
||||
@ -28,6 +30,7 @@ module.exports = exports = {
|
||||
invalid_ldap_client_id: 'Invalid ldap_client_id:',
|
||||
invalid_oauth_client_id: 'Invalid oauth_client_id:',
|
||||
invalid_saml_service_provider_id: 'Invalid saml_service_provider_id:',
|
||||
invalid_target_type: 'Invalid target_type.',
|
||||
|
||||
insufficient_permissions: 'Insufficient permissions.',
|
||||
missing_field: {
|
||||
|
@ -1,4 +1,5 @@
|
||||
module.exports = exports = {
|
||||
policy_not_found: 'Policy not found with that ID.',
|
||||
permission_not_found: 'Permission not found with that ID.',
|
||||
invalid_entity: 'Invalid entity_type. Must be one of:'
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user