Skip to content
This repository has been archived by the owner on Aug 19, 2022. It is now read-only.

Commit

Permalink
Introduce policy templates
Browse files Browse the repository at this point in the history
Policy templates are implemented as regular policy with variabels
similar to regular PBAC variables.
However, it is possible to fix the value of those variables when a
policy is assigned to a user, team or organisation.

This commit makes 3 changes:
- endpoints that assign policies now support passing variables
- endpoints that return assigned policies now return variables as well
- policies retrieved for an authorization check are now parsed and
  variables are interpolated
  • Loading branch information
paolochiodi committed Oct 26, 2017
1 parent fd07ffd commit c7c19f2
Show file tree
Hide file tree
Showing 20 changed files with 874 additions and 95 deletions.
4 changes: 4 additions & 0 deletions database/migrations/004.do.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

ALTER TABLE user_policies ADD column variables JSONB DEFAULT '{}';
ALTER TABLE team_policies ADD column variables JSONB DEFAULT '{}';
ALTER TABLE organization_policies ADD column variables JSONB DEFAULT '{}';
4 changes: 4 additions & 0 deletions database/migrations/004.undo.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

ALTER TABLE user_policies DROP column variables;
ALTER TABLE team_policies DROP column variables;
ALTER TABLE organization_policies DROP column variables;
23 changes: 21 additions & 2 deletions lib/core/lib/mapping.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const _ = require('lodash')

function mapOrganization (row) {
return {
id: row.id,
Expand All @@ -17,19 +19,36 @@ function mapPolicy (row) {
}
}

function compileStatements (statements, variables) {
_.each(statements, function (statement) {
if (statement.Resource) {
statement.Resource = _.map(statement.Resource, function (resource) {
return resource.replace(/\${(.+?)}/g, function (match, variable) {
return _.get(variables, variable, match)
})
})
}
})

return statements
}

function mapIamPolicy (row) {
const variables = row.variables || {}

return {
Version: row.version,
Name: row.name,
Statement: row.statements.Statement
Statement: compileStatements(row.statements.Statement, variables)
}
}

function mapPolicySimple (row) {
return {
id: row.id,
name: row.name,
version: row.version
version: row.version,
variables: row.variables || {}
}
}

Expand Down
23 changes: 14 additions & 9 deletions lib/core/lib/ops/organizationOps.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function buildOrganizationOps (db, config) {
if (err) return next(err)
job.user.id = res.rows[0].id

userOps.insertPolicies(job.client, job.user.id, [job.adminPolicyId], utils.boomErrorWrapper(next))
userOps.insertPolicies(job.client, job.user.id, [{id: job.adminPolicyId}], utils.boomErrorWrapper(next))
})

return
Expand Down Expand Up @@ -230,7 +230,7 @@ function buildOrganizationOps (db, config) {

tasks.push((next) => {
const sqlQuery = SQL`
SELECT pol.id, pol.name, pol.version
SELECT pol.id, pol.name, pol.version, org_pol.variables
FROM organization_policies org_pol, policies pol
WHERE org_pol.org_id = ${id} AND org_pol.policy_id = pol.id
ORDER BY UPPER(pol.name)
Expand Down Expand Up @@ -380,7 +380,7 @@ function buildOrganizationOps (db, config) {
},
(job, next) => {
job.id = id
job.policies = policies
job.policies = utils.preparePolicies(policies)

next()
},
Expand Down Expand Up @@ -423,7 +423,7 @@ function buildOrganizationOps (db, config) {
},
(job, next) => {
job.id = id
job.policies = policies
job.policies = utils.preparePolicies(policies)

next()
},
Expand Down Expand Up @@ -505,16 +505,21 @@ function buildOrganizationOps (db, config) {
},

insertPolicies: function insertPolicies (client, id, policies, cb) {
if (policies.length === 0) return cb()

const sqlQuery = SQL`
INSERT INTO organization_policies (
policy_id, org_id
policy_id, org_id, variables
) VALUES
`
sqlQuery.append(SQL`(${policies[0]}, ${id})`)
policies.slice(1).forEach((policyId) => {
sqlQuery.append(SQL`, (${policyId}, ${id})`)
sqlQuery.append(SQL`(${policies[0].id}, ${id}, ${policies[0].variables})`)
policies.slice(1).forEach((policy) => {
sqlQuery.append(SQL`, (${policy.id}, ${id}, ${policy.variables})`)
})
sqlQuery.append(SQL` ON CONFLICT ON CONSTRAINT org_policy_link DO NOTHING`)
sqlQuery.append(SQL` ON CONFLICT ON CONSTRAINT org_policy_link
DO UPDATE SET variables = excluded.variables
WHERE organization_policies.policy_id = excluded.policy_id
AND organization_policies.org_id = excluded.org_id`)

client.query(sqlQuery, utils.boomErrorWrapper(cb))
}
Expand Down
31 changes: 24 additions & 7 deletions lib/core/lib/ops/policyOps.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,23 +288,23 @@ function buildPolicyOps (db, config) {
),
policies_from_teams AS (
SELECT
policy_id
policy_id, variables
FROM
team_policies
WHERE
team_id IN (SELECT id FROM user_teams)
),
policies_from_user AS (
SELECT
policy_id
policy_id, variables
FROM
user_policies
WHERE
user_id = ${userId}
),
policies_from_organization AS (
SELECT
op.policy_id
op.policy_id, variables
FROM
organization_policies op
LEFT JOIN
Expand All @@ -319,9 +319,26 @@ function buildPolicyOps (db, config) {
id,
version,
name,
statements
statements,
COALESCE(
policies_from_user.variables,
policies_from_teams.variables,
policies_from_organization.variables
) AS variables
FROM
policies
LEFT JOIN
policies_from_user
ON
policies.id = policies_from_user.policy_id
LEFT JOIN
policies_from_teams
ON
policies.id = policies_from_teams.policy_id
LEFT JOIN
policies_from_organization
ON
policies.id = policies_from_organization.policy_id
WHERE (
org_id = ${organizationId}
OR (
Expand All @@ -331,11 +348,11 @@ function buildPolicyOps (db, config) {
)
)
AND (
id IN (SELECT policy_id FROM policies_from_user)
policies_from_user.policy_id IS NOT NULL
OR
id IN (SELECT policy_id FROM policies_from_teams)
policies_from_teams.policy_id IS NOT NULL
OR
id IN (SELECT policy_id FROM policies_from_organization)
policies_from_organization.policy_id IS NOT NULL
)
`

Expand Down
24 changes: 15 additions & 9 deletions lib/core/lib/ops/teamOps.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,16 @@ function buildTeamOps (db, config) {

if (policies.length === 0) return next()

const sql = SQL`INSERT INTO team_policies (policy_id, team_id) VALUES `
sql.append(SQL`(${policies[0]},${teamId})`)
policies.slice(1).forEach((policyId) => {
sql.append(SQL`, (${policyId},${teamId})`)
const sql = SQL`INSERT INTO team_policies (policy_id, team_id, variables) VALUES `
sql.append(SQL`(${policies[0].id},${teamId},${policies[0].variables})`)
policies.slice(1).forEach((policy) => {
sql.append(SQL`, (${policy.id},${teamId}, ${policy.variables})`)
})
sql.append(SQL` ON CONFLICT ON CONSTRAINT team_policy_link DO NOTHING`)
sql.append(SQL`
ON CONFLICT ON CONSTRAINT team_policy_link
DO UPDATE SET variables = excluded.variables
WHERE team_policies.policy_id = excluded.policy_id
AND team_policies.team_id = excluded.team_id`)
job.client.query(sql, utils.boomErrorWrapper(next))
}

Expand Down Expand Up @@ -268,7 +272,7 @@ function buildTeamOps (db, config) {
function loadTeamPolicies (job, next) {
const { id } = job
const sql = SQL`
SELECT pol.id, pol.name, pol.version
SELECT pol.id, pol.name, pol.version, tpol.variables
FROM team_policies tpol, policies pol
WHERE tpol.team_id = ${id}
AND tpol.policy_id = pol.id
Expand Down Expand Up @@ -547,11 +551,13 @@ function buildTeamOps (db, config) {
* @param {Function} cb
*/
addTeamPolicies: function addTeamPolicies (params, cb) {
const { id, organizationId, policies } = params
const { id, organizationId } = params

Joi.validate({ id, organizationId, policies }, validationRules.addTeamPolicies, function (err) {
Joi.validate({ id, organizationId, policies: params.policies }, validationRules.addTeamPolicies, function (err) {
if (err) return cb(Boom.badRequest(err))

const policies = utils.preparePolicies(params.policies)

utils.checkPoliciesOrg(db, policies, organizationId, (err) => {
if (err) return cb(err)

Expand Down Expand Up @@ -581,7 +587,7 @@ function buildTeamOps (db, config) {
(job, next) => {
job.teamId = id
job.organizationId = organizationId
job.policies = policies
job.policies = utils.preparePolicies(policies)
next()
},
(job, next) => {
Expand Down
20 changes: 12 additions & 8 deletions lib/core/lib/ops/userOps.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function buildUserOps (db/*, config */) {

tasks.push((next) => {
const sqlQuery = SQL`
SELECT pol.id, pol.name, pol.version
SELECT pol.id, pol.name, pol.version, user_pol.variables
FROM user_policies user_pol, policies pol
WHERE user_pol.user_id = ${id} AND user_pol.policy_id = pol.id
ORDER BY UPPER(pol.name)
Expand Down Expand Up @@ -291,7 +291,7 @@ function buildUserOps (db/*, config */) {
(job, next) => {
job.id = id
job.organizationId = organizationId
job.policies = policies
job.policies = utils.preparePolicies(policies)

next()
},
Expand Down Expand Up @@ -334,7 +334,7 @@ function buildUserOps (db/*, config */) {
},
(job, next) => {
job.id = id
job.policies = policies
job.policies = utils.preparePolicies(policies)
job.organizationId = organizationId

next()
Expand Down Expand Up @@ -468,14 +468,18 @@ function buildUserOps (db/*, config */) {
insertPolicies: function insertPolicies (client, id, policies, cb) {
const sqlQuery = SQL`
INSERT INTO user_policies (
policy_id, user_id
policy_id, user_id, variables
) VALUES
`
sqlQuery.append(SQL`(${policies[0]}, ${id})`)
policies.slice(1).forEach((policyId) => {
sqlQuery.append(SQL`, (${policyId}, ${id})`)
sqlQuery.append(SQL`(${policies[0].id}, ${id}, ${policies[0].variables})`)
policies.slice(1).forEach((policy) => {
sqlQuery.append(SQL`, (${policy.id}, ${id}, ${policy.variables})`)
})
sqlQuery.append(SQL` ON CONFLICT ON CONSTRAINT user_policy_link DO NOTHING`)
sqlQuery.append(SQL`
ON CONFLICT ON CONSTRAINT user_policy_link
DO UPDATE SET variables = excluded.variables
WHERE user_policies.policy_id = excluded.policy_id
AND user_policies.user_id = excluded.user_id`)

client.query(sqlQuery, utils.boomErrorWrapper(cb))
},
Expand Down
22 changes: 20 additions & 2 deletions lib/core/lib/ops/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ function isForeignKeyViolationError (err) {
}

function checkPoliciesOrg (db, policies, organizationId, cb) {
db.query(SQL`SELECT id FROM policies WHERE id = ANY (${policies}) AND org_id = ${organizationId}`, (err, result) => {
const policyIds = _.map(policies, 'id')

db.query(SQL`SELECT id FROM policies WHERE id = ANY (${policyIds}) AND org_id = ${organizationId}`, (err, result) => {
if (err) return cb(Boom.badImplementation(err))

if (result.rowCount !== policies.length) {
return cb(Boom.badRequest(`Some policies [${_.difference(policies, result.rows.map(r => r.id))}] were not found`))
return cb(Boom.badRequest(`Some policies [${_.difference(policyIds, result.rows.map(r => r.id))}] were not found`))
}

cb()
Expand Down Expand Up @@ -82,7 +84,23 @@ function checkOrg (db, organizationId, cb) {
})
}

function preparePolicy (policy) {
if (_.isString(policy)) {
return {
id: policy,
variables: {}
}
}

return policy
}

function preparePolicies (policies) {
return _.map(policies, preparePolicy)
}

module.exports = {
preparePolicies,
boomErrorWrapper,
isUniqueViolationError,
isForeignKeyViolationError,
Expand Down
12 changes: 10 additions & 2 deletions lib/core/lib/ops/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ const Joi = require('joi')
const requiredString = Joi.string().required()
const requiredStringId = Joi.string().required().max(128)

const requiredPolicy = Joi.alternatives().try([
requiredString,
Joi.object().description('Policy').keys({
id: requiredString.description('Policy Id'),
variables: Joi.object().description('A list of the veriables with their fixed values')
})
])

const validationRules = {
name: requiredString.description('Name'),
userName: requiredString.max(255).description('Name'),
Expand All @@ -28,7 +36,7 @@ const validationRules = {
policyId: requiredStringId.description('Policy ID'),

users: Joi.array().required().items(requiredString).description('User IDs'),
policies: Joi.array().required().items(requiredString).description('Policies IDs'),
policies: Joi.array().required().items(requiredPolicy).description('Policies IDs'),
teams: Joi.array().required().items(requiredString).description('Teams IDs'),
resources: Joi.array().max(10).items(requiredString.description('A single resource')).single().required().description('A list of Resources'),

Expand Down Expand Up @@ -231,7 +239,7 @@ const organizations = {
},
replaceOrganizationPolicies: {
id: validationRules.organizationId,
policies: validationRules.policies
policies: validationRules.policies.min(1)
},
deleteOrganizationPolicies: {
id: validationRules.organizationId
Expand Down
3 changes: 2 additions & 1 deletion lib/plugin/swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const Policy = Joi.object({
const PolicyRef = Joi.object({
id: Joi.string().description('Policy ID'),
version: Joi.string().description('Policy version'),
name: Joi.string().description('Policy name')
name: Joi.string().description('Policy name'),
variables: Joi.object().description('List of fixed values for variables')
}).label('PolicyRef')

const UserRef = Joi.object({
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"pg:init": "UDARU_SERVICE_local=true node database/init.js && npm run pg:migrate",
"pg:init-test-db": "npm run pg:init && npm run pg:load-test-data",
"pg:load-test-data": "UDARU_SERVICE_local=true node database/loadTestData.js",
"pg:migrate": "node database/migrate.js --version=3",
"pg:migrate": "node database/migrate.js --version=4",
"start": "node lib/server/start.js",
"test": "npm run pg:init-test-db && UDARU_SERVICE_logger_pino_level=silent lab -c -t 95",
"test:commit-check": "npm run doc:lint && npm run lint && npm run test",
Expand Down

0 comments on commit c7c19f2

Please sign in to comment.