Add api_scope target for IAM policy
This commit is contained in:
parent
faab948a6b
commit
b526b8f24d
@ -10,7 +10,6 @@
|
||||
- Cobalt form - after action handlers
|
||||
- e.g. after insert perform action
|
||||
- e.g. after update perform action, &c.
|
||||
- IAM manage user API scopes
|
||||
- OAuth2 -> support refresh tokens
|
||||
- Traps -> support session traps; convert MFA challenge to use session trap
|
||||
- Allow setting user trap from web UI
|
||||
|
@ -118,6 +118,7 @@ class PolicyResource extends CRUDBase {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ display: 'Application', value: 'application' },
|
||||
{ display: 'API Scope', value: 'api_scope' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -132,6 +133,18 @@ class PolicyResource extends CRUDBase {
|
||||
},
|
||||
if: (form_data) => form_data.target_type === 'application'
|
||||
},
|
||||
{
|
||||
name: 'Target',
|
||||
field: 'target_id',
|
||||
required: true,
|
||||
type: 'select.dynamic',
|
||||
options: {
|
||||
resource: 'reflect/Scope',
|
||||
display: 'scope',
|
||||
value: 'scope',
|
||||
},
|
||||
if: (form_data) => form_data.target_type === 'api_scope'
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ const { Controller } = require('libflitter')
|
||||
|
||||
class IAMController extends Controller {
|
||||
static get services() {
|
||||
return [...super.services, 'models']
|
||||
return [...super.services, 'models', 'canon']
|
||||
}
|
||||
|
||||
async check_entity_access(req, res, next) {
|
||||
@ -111,7 +111,7 @@ class IAMController extends Controller {
|
||||
.message('Invalid access_type. Must be one of: allow, deny.')
|
||||
.api()
|
||||
|
||||
if ( !['application'].includes(req.body.target_type) )
|
||||
if ( !['application', 'api_scope'].includes(req.body.target_type) )
|
||||
return res.status(400)
|
||||
.message('Invalid target_type. Must be one of: application.')
|
||||
.api()
|
||||
@ -124,6 +124,12 @@ class IAMController extends Controller {
|
||||
return res.status(400)
|
||||
.message('Invalid target_id.')
|
||||
.api()
|
||||
} else if ( req.body.target_type === 'api_scope' ) {
|
||||
const api_scopes = this.canon.get('controller::api:v1:Reflect.api_scopes')()
|
||||
if ( !api_scopes.includes(req.body.target_id) )
|
||||
return res.status(400)
|
||||
.message('Invalid target_id.')
|
||||
.api()
|
||||
}
|
||||
|
||||
const policy = new Policy({
|
||||
@ -189,7 +195,7 @@ class IAMController extends Controller {
|
||||
.message('Invalid access_type. Must be one of: allow, deny.')
|
||||
.api()
|
||||
|
||||
if ( !['application'].includes(req.body.target_type) )
|
||||
if ( !['application', 'api_scope'].includes(req.body.target_type) )
|
||||
return res.status(400)
|
||||
.message('Invalid target_type. Must be one of: application.')
|
||||
.api()
|
||||
@ -202,6 +208,12 @@ class IAMController extends Controller {
|
||||
return res.status(400)
|
||||
.message('Invalid target_id.')
|
||||
.api()
|
||||
} else if ( req.body.target_type === 'api_scope' ) {
|
||||
const api_scopes = this.canon.get('controller::api:v1:Reflect.api_scopes')()
|
||||
if ( !api_scopes.includes(req.body.target_id) )
|
||||
return res.status(400)
|
||||
.message('Invalid target_id.')
|
||||
.api()
|
||||
}
|
||||
|
||||
policy.entity_type = req.body.entity_type
|
||||
|
@ -130,7 +130,7 @@ class ReflectController extends Controller {
|
||||
return res.api()
|
||||
}
|
||||
|
||||
async get_scopes(req, res, next) {
|
||||
api_scopes() {
|
||||
const routers = this.routers.canonical_items
|
||||
const scopes = []
|
||||
|
||||
@ -158,6 +158,11 @@ class ReflectController extends Controller {
|
||||
}
|
||||
|
||||
scopes.sort()
|
||||
return scopes
|
||||
}
|
||||
|
||||
async get_scopes(req, res, next) {
|
||||
const scopes = this.api_scopes()
|
||||
return res.api(scopes.map(x => {
|
||||
return { scope: x }
|
||||
}))
|
||||
|
@ -12,7 +12,7 @@ class PolicyModel extends Model {
|
||||
entity_type: String, // user | group
|
||||
entity_id: String,
|
||||
access_type: String, // allow | deny
|
||||
target_type: { type: String, default: 'application' }, // application
|
||||
target_type: { type: String, default: 'application' }, // application | api_scope
|
||||
target_id: String,
|
||||
active: { type: Boolean, default: true },
|
||||
}
|
||||
@ -44,6 +44,27 @@ class PolicyModel extends Model {
|
||||
return (await this.check_allow(entity_id, target_id)) && !(await this.check_deny(entity_id, target_id))
|
||||
}
|
||||
|
||||
static async check_user_denied(user, target_id) {
|
||||
const groups = await user.groups()
|
||||
const group_ids = groups.map(x => x.id)
|
||||
|
||||
const user_denials = await this.find({
|
||||
entity_id: user.id,
|
||||
target_id,
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
})
|
||||
|
||||
const group_denials = await this.find({
|
||||
entity_id: { $in: group_ids },
|
||||
target_id,
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
})
|
||||
|
||||
return user_denials.length > 0 || group_denials.length > 0
|
||||
}
|
||||
|
||||
static async check_user_access(user, target_id) {
|
||||
const groups = await user.groups()
|
||||
const group_ids = groups.map(x => x.id)
|
||||
@ -51,28 +72,28 @@ class PolicyModel extends Model {
|
||||
const user_approvals = await this.find({
|
||||
entity_id: user.id,
|
||||
target_id,
|
||||
approval_type: 'allow',
|
||||
access_type: 'allow',
|
||||
active: true,
|
||||
})
|
||||
|
||||
const user_denials = await this.find({
|
||||
entity_id: user.id,
|
||||
target_id,
|
||||
approval_type: 'deny',
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
})
|
||||
|
||||
const group_approvals = await this.find({
|
||||
entity_id: { $in: group_ids },
|
||||
target_id,
|
||||
approval_type: 'allow',
|
||||
access_type: 'allow',
|
||||
active: true,
|
||||
})
|
||||
|
||||
const group_denials = await this.find({
|
||||
entity_id: { $in: group_ids },
|
||||
target_id,
|
||||
approval_type: 'deny',
|
||||
access_type: 'deny',
|
||||
active: true,
|
||||
})
|
||||
|
||||
@ -109,6 +130,8 @@ class PolicyModel extends Model {
|
||||
const Application = this.models.get('Application')
|
||||
const app = await Application.findById(this.target_id)
|
||||
target_display = `Application: ${app.name}`
|
||||
} else if ( this.target_type === 'api_scope' ) {
|
||||
target_display = `API Scope: ${this.target_id}`
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1,7 +1,13 @@
|
||||
const { Middleware } = require('libflitter')
|
||||
|
||||
class PermissionMiddleware extends Middleware {
|
||||
static get services() {
|
||||
return [...super.services, 'models']
|
||||
}
|
||||
|
||||
async test(req, res, next, { check }) {
|
||||
const Policy = this.models.get('iam:Policy')
|
||||
|
||||
// If the request was authorized using an OAuth2 bearer token,
|
||||
// make sure the associated client has permission to access this endpoint.
|
||||
if ( req?.oauth?.client ) {
|
||||
@ -11,8 +17,11 @@ class PermissionMiddleware extends Middleware {
|
||||
.api()
|
||||
}
|
||||
|
||||
const policy_denied = await Policy.check_user_denied(req.user, check)
|
||||
const policy_access = await Policy.check_user_access(req.user, check)
|
||||
|
||||
// Make sure the user has permission
|
||||
if ( !req.user.can(check) )
|
||||
if ( policy_denied || (!req.user.can(check) && !policy_access) )
|
||||
return res.status(401)
|
||||
.message('Insufficient permissions.')
|
||||
.api()
|
||||
|
Loading…
Reference in New Issue
Block a user