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