mirror of
https://github.com/jc21/nginx-proxy-manager.git
synced 2024-08-30 18:22:48 +00:00
Merge pull request #360 from Indemnity83/ip-access-control
Client Access Lists
This commit is contained in:
commit
a9f068daa8
@ -1,14 +1,15 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const batchflow = require('batchflow');
|
||||
const logger = require('../logger').access;
|
||||
const error = require('../lib/error');
|
||||
const accessListModel = require('../models/access_list');
|
||||
const accessListAuthModel = require('../models/access_list_auth');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
const internalAuditLog = require('./audit-log');
|
||||
const internalNginx = require('./nginx');
|
||||
const utils = require('../lib/utils');
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const batchflow = require('batchflow');
|
||||
const logger = require('../logger').access;
|
||||
const error = require('../lib/error');
|
||||
const accessListModel = require('../models/access_list');
|
||||
const accessListAuthModel = require('../models/access_list_auth');
|
||||
const accessListClientModel = require('../models/access_list_client');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
const internalAuditLog = require('./audit-log');
|
||||
const internalNginx = require('./nginx');
|
||||
const utils = require('../lib/utils');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
@ -29,14 +30,16 @@ const internalAccessList = {
|
||||
.omit(omissions())
|
||||
.insertAndFetch({
|
||||
name: data.name,
|
||||
satify_any: data.satify_any,
|
||||
owner_user_id: access.token.getUserId(1)
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
data.id = row.id;
|
||||
|
||||
// Now add the items
|
||||
let promises = [];
|
||||
|
||||
// Now add the items
|
||||
data.items.map((item) => {
|
||||
promises.push(accessListAuthModel
|
||||
.query()
|
||||
@ -48,13 +51,27 @@ const internalAccessList = {
|
||||
);
|
||||
});
|
||||
|
||||
// Now add the clients
|
||||
if (typeof data.clients !== 'undefined' && data.clients) {
|
||||
data.clients.map((client) => {
|
||||
promises.push(accessListClientModel
|
||||
.query()
|
||||
.insert({
|
||||
access_list_id: row.id,
|
||||
address: client.address,
|
||||
directive: client.directive
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
// re-fetch with expansions
|
||||
return internalAccessList.get(access, {
|
||||
id: data.id,
|
||||
expand: ['owner', 'items']
|
||||
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.clients']
|
||||
}, true /* <- skip masking */);
|
||||
})
|
||||
.then((row) => {
|
||||
@ -64,7 +81,7 @@ const internalAccessList = {
|
||||
return internalAccessList.build(row)
|
||||
.then(() => {
|
||||
if (row.proxy_host_count) {
|
||||
return internalNginx.reload();
|
||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
@ -109,7 +126,8 @@ const internalAccessList = {
|
||||
.query()
|
||||
.where({id: data.id})
|
||||
.patch({
|
||||
name: data.name
|
||||
name: data.name,
|
||||
satify_any: data.satify_any,
|
||||
});
|
||||
}
|
||||
})
|
||||
@ -153,6 +171,38 @@ const internalAccessList = {
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Check for clients and add/update/remove them
|
||||
if (typeof data.clients !== 'undefined' && data.clients) {
|
||||
let promises = [];
|
||||
|
||||
data.clients.map(function (client) {
|
||||
if (client.address) {
|
||||
promises.push(accessListClientModel
|
||||
.query()
|
||||
.insert({
|
||||
access_list_id: data.id,
|
||||
address: client.address,
|
||||
directive: client.directive
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let query = accessListClientModel
|
||||
.query()
|
||||
.delete()
|
||||
.where('access_list_id', data.id);
|
||||
|
||||
return query
|
||||
.then(() => {
|
||||
// Add new items
|
||||
if (promises.length) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Add to audit log
|
||||
return internalAuditLog.add(access, {
|
||||
@ -166,14 +216,14 @@ const internalAccessList = {
|
||||
// re-fetch with expansions
|
||||
return internalAccessList.get(access, {
|
||||
id: data.id,
|
||||
expand: ['owner', 'items']
|
||||
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.clients']
|
||||
}, true /* <- skip masking */);
|
||||
})
|
||||
.then((row) => {
|
||||
return internalAccessList.build(row)
|
||||
.then(() => {
|
||||
if (row.proxy_host_count) {
|
||||
return internalNginx.reload();
|
||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
@ -204,7 +254,7 @@ const internalAccessList = {
|
||||
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||
.where('access_list.is_deleted', 0)
|
||||
.andWhere('access_list.id', data.id)
|
||||
.allowEager('[owner,items,proxy_hosts]')
|
||||
.allowEager('[owner,items,clients,proxy_hosts,proxy_hosts.access_list.clients]')
|
||||
.omit(['access_list.is_deleted'])
|
||||
.first();
|
||||
|
||||
@ -246,7 +296,7 @@ const internalAccessList = {
|
||||
delete: (access, data) => {
|
||||
return access.can('access_lists:delete', data.id)
|
||||
.then(() => {
|
||||
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items']});
|
||||
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row) {
|
||||
@ -330,7 +380,7 @@ const internalAccessList = {
|
||||
.where('access_list.is_deleted', 0)
|
||||
.groupBy('access_list.id')
|
||||
.omit(['access_list.is_deleted'])
|
||||
.allowEager('[owner,items]')
|
||||
.allowEager('[owner,items,clients]')
|
||||
.orderBy('access_list.name', 'ASC');
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
|
@ -73,7 +73,7 @@ const internalProxyHost = {
|
||||
// re-fetch with cert
|
||||
return internalProxyHost.get(access, {
|
||||
id: row.id,
|
||||
expand: ['certificate', 'owner', 'access_list']
|
||||
expand: ['certificate', 'owner', 'access_list.clients']
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
@ -186,7 +186,7 @@ const internalProxyHost = {
|
||||
.then(() => {
|
||||
return internalProxyHost.get(access, {
|
||||
id: data.id,
|
||||
expand: ['owner', 'certificate', 'access_list']
|
||||
expand: ['owner', 'certificate', 'access_list.clients']
|
||||
})
|
||||
.then((row) => {
|
||||
// Configure nginx
|
||||
@ -219,7 +219,7 @@ const internalProxyHost = {
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.andWhere('id', data.id)
|
||||
.allowEager('[owner,access_list,certificate]')
|
||||
.allowEager('[owner,access_list,access_list.clients,certificate]')
|
||||
.first();
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
|
53
backend/migrations/20200410143839_access_list_client.js
Normal file
53
backend/migrations/20200410143839_access_list_client.js
Normal file
@ -0,0 +1,53 @@
|
||||
const migrate_name = 'access_list_client';
|
||||
const logger = require('../logger').migrate;
|
||||
|
||||
/**
|
||||
* Migrate
|
||||
*
|
||||
* @see http://knexjs.org/#Schema
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.up = function (knex/*, Promise*/) {
|
||||
|
||||
logger.info('[' + migrate_name + '] Migrating Up...');
|
||||
|
||||
return knex.schema.createTable('access_list_client', (table) => {
|
||||
table.increments().primary();
|
||||
table.dateTime('created_on').notNull();
|
||||
table.dateTime('modified_on').notNull();
|
||||
table.integer('access_list_id').notNull().unsigned();
|
||||
table.string('address').notNull();
|
||||
table.string('directive').notNull();
|
||||
table.json('meta').notNull();
|
||||
|
||||
})
|
||||
.then(function () {
|
||||
logger.info('[' + migrate_name + '] access_list_client Table created');
|
||||
|
||||
return knex.schema.table('access_list', function (access_list) {
|
||||
access_list.integer('satify_any').notNull().defaultTo(0);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list Table altered');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.down = function (knex/*, Promise*/) {
|
||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
||||
|
||||
return knex.schema.dropTable('access_list_client')
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list_client Table dropped');
|
||||
});
|
||||
};
|
@ -1,10 +1,11 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const AccessListAuth = require('./access_list_auth');
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const AccessListAuth = require('./access_list_auth');
|
||||
const AccessListClient = require('./access_list_client');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
@ -62,6 +63,17 @@ class AccessList extends Model {
|
||||
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||
}
|
||||
},
|
||||
clients: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccessListClient,
|
||||
join: {
|
||||
from: 'access_list.id',
|
||||
to: 'access_list_client.access_list_id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||
}
|
||||
},
|
||||
proxy_hosts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ProxyHost,
|
||||
@ -76,6 +88,10 @@ class AccessList extends Model {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get satisfy() {
|
||||
return this.satify_any ? 'satisfy any' : 'satisfy all';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AccessList;
|
||||
|
58
backend/models/access_list_client.js
Normal file
58
backend/models/access_list_client.js
Normal file
@ -0,0 +1,58 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class AccessListClient extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
this.meta = {};
|
||||
}
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'AccessListClient';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'access_list_client';
|
||||
}
|
||||
|
||||
static get jsonAttributes () {
|
||||
return ['meta'];
|
||||
}
|
||||
|
||||
static get relationMappings () {
|
||||
return {
|
||||
access_list: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: require('./access_list'),
|
||||
join: {
|
||||
from: 'access_list_client.access_list_id',
|
||||
to: 'access_list.id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.where('access_list.is_deleted', 0);
|
||||
qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get rule() {
|
||||
return `${this.directive} ${this.address}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AccessListClient;
|
@ -19,6 +19,29 @@
|
||||
"type": "string",
|
||||
"description": "Name of the Access List"
|
||||
},
|
||||
"directive": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "deny"]
|
||||
},
|
||||
"address": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^any$"
|
||||
}
|
||||
]
|
||||
},
|
||||
"satify_any": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"meta": {
|
||||
"type": "object"
|
||||
}
|
||||
@ -78,9 +101,12 @@
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"satify_any": {
|
||||
"$ref": "#/definitions/satify_any"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"minItems": 0,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@ -96,6 +122,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"clients": {
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/address"
|
||||
},
|
||||
"directive": {
|
||||
"$ref": "#/definitions/directive"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
@ -124,9 +166,12 @@
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"satify_any": {
|
||||
"$ref": "#/definitions/satify_any"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"minItems": 0,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@ -141,6 +186,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"clients": {
|
||||
"type": "array",
|
||||
"minItems": 0,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/address"
|
||||
},
|
||||
"directive": {
|
||||
"$ref": "#/definitions/directive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -21,11 +21,21 @@ server {
|
||||
{% if use_default_location %}
|
||||
|
||||
location / {
|
||||
{%- if access_list_id > 0 -%}
|
||||
# Access List
|
||||
|
||||
{% if access_list_id > 0 %}
|
||||
# Authorization
|
||||
auth_basic "Authorization required";
|
||||
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||
{%- endif %}
|
||||
|
||||
# Access Rules
|
||||
{% for client in access_list.clients %}
|
||||
{{- client.rule -}};
|
||||
{% endfor %}deny all;
|
||||
|
||||
# Access checks must...
|
||||
{{ access_list.satisfy }};
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% include "_forced_ssl.conf" %}
|
||||
{% include "_hsts.conf" %}
|
||||
|
@ -3,28 +3,74 @@
|
||||
<h5 class="modal-title"><%- i18n('access-lists', 'form-title', {id: id}) %></h5>
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body has-tabs">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label>
|
||||
<input type="text" name="name" class="form-control" value="<%- name %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'username') %></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'password') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active show" aria-selected="true"><i class="fe fe-zap"></i> <%- i18n('access-lists', 'details') %></a></li>
|
||||
<li role="presentation" class="nav-item"><a href="#auth" aria-controls="tab4" role="tab" data-toggle="tab" class="nav-link" aria-selected="false"><i class="fe fe-users"></i> <%- i18n('access-lists', 'authorization') %></a></li>
|
||||
<li role="presentation" class="nav-item"><a href="#access" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link" aria-selected="false"><i class="fe fe-radio"></i> <%- i18n('access-lists', 'access') %></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="items"><!-- items --></div>
|
||||
<div class="tab-content">
|
||||
<!-- Details -->
|
||||
<div role="tabpanel" class="tab-pane active show" id="details">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label>
|
||||
<input type="text" name="name" class="form-control" value="<%- name %>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input type="checkbox" class="custom-switch-input" name="satify_any" value="1"<%- typeof satify_any !== 'undefined' && satify_any ? ' checked' : '' %>>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%- i18n('access-lists', 'satisfy-any') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authorization -->
|
||||
<div class="tab-pane" id="auth">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'username') %></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'password') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items"><!-- items --></div>
|
||||
</div>
|
||||
|
||||
<!-- Access -->
|
||||
<div class="tab-pane" id="access">
|
||||
<div class="clients"><!-- clients --></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control disabled" value="deny" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-md-9">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control disabled" value="all" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted">Note that the <code>allow</code> and <code>deny</code> directives will be applied in the order they are defined.</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -3,6 +3,7 @@ const App = require('../../main');
|
||||
const AccessListModel = require('../../../models/access-list');
|
||||
const template = require('./form.ejs');
|
||||
const ItemView = require('./form/item');
|
||||
const ClientView = require('./form/client');
|
||||
|
||||
require('jquery-serializejson');
|
||||
|
||||
@ -10,20 +11,26 @@ const ItemsView = Mn.CollectionView.extend({
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
const ClientsView = Mn.CollectionView.extend({
|
||||
childView: ClientView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'modal-dialog',
|
||||
|
||||
ui: {
|
||||
items_region: '.items',
|
||||
form: 'form',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save'
|
||||
items_region: '.items',
|
||||
clients_region: '.clients',
|
||||
form: 'form',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save'
|
||||
},
|
||||
|
||||
regions: {
|
||||
items_region: '@ui.items_region'
|
||||
items_region: '@ui.items_region',
|
||||
clients_region: '@ui.clients_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
@ -35,9 +42,10 @@ module.exports = Mn.View.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
let view = this;
|
||||
let form_data = this.ui.form.serializeJSON();
|
||||
let items_data = [];
|
||||
let view = this;
|
||||
let form_data = this.ui.form.serializeJSON();
|
||||
let items_data = [];
|
||||
let clients_data = [];
|
||||
|
||||
form_data.username.map(function (val, idx) {
|
||||
if (val.trim().length) {
|
||||
@ -48,16 +56,29 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
});
|
||||
|
||||
if (!items_data.length) {
|
||||
alert('You must specify at least 1 Username and Password combination');
|
||||
form_data.address.map(function (val, idx) {
|
||||
if (val.trim().length) {
|
||||
clients_data.push({
|
||||
address: val.trim(),
|
||||
directive: form_data.directive[idx]
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if (!items_data.length && !clients_data.length) {
|
||||
alert('You must specify at least 1 Authorization or Access rule');
|
||||
return;
|
||||
}
|
||||
|
||||
let data = {
|
||||
name: form_data.name,
|
||||
items: items_data
|
||||
name: form_data.name,
|
||||
satify_any: !!form_data.satify_any,
|
||||
items: items_data,
|
||||
clients: clients_data
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
|
||||
let method = App.Api.Nginx.AccessLists.create;
|
||||
let is_new = true;
|
||||
|
||||
@ -88,6 +109,7 @@ module.exports = Mn.View.extend({
|
||||
|
||||
onRender: function () {
|
||||
let items = this.model.get('items');
|
||||
let clients = this.model.get('clients');
|
||||
|
||||
// Add empty items to the end of the list. This is cheating but hey I don't have the time to do it right
|
||||
let items_to_add = 5 - items.length;
|
||||
@ -97,9 +119,20 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
}
|
||||
|
||||
let clients_to_add = 4 - clients.length;
|
||||
if (clients_to_add) {
|
||||
for (let i = 0; i < clients_to_add; i++) {
|
||||
clients.push({});
|
||||
}
|
||||
}
|
||||
|
||||
this.showChildView('items_region', new ItemsView({
|
||||
collection: new Backbone.Collection(items)
|
||||
}));
|
||||
|
||||
this.showChildView('clients_region', new ClientsView({
|
||||
collection: new Backbone.Collection(clients)
|
||||
}));
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
|
13
frontend/js/app/nginx/access/form/client.ejs
Normal file
13
frontend/js/app/nginx/access/form/client.ejs
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="col-sm-3 col-md-3">
|
||||
<div class="form-group">
|
||||
<select name="directive[]" class="form-control custom-select" placeholder="http">
|
||||
<option value="allow" <%- typeof directive == 'undefined' || directive === 'allow' ? 'selected' : '' %>>allow</option>
|
||||
<option value="deny" <%- typeof directive !== 'undefined' && directive === 'deny' ? 'selected' : '' %>>deny</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-md-9">
|
||||
<div class="form-group">
|
||||
<input type="text" name="address[]" class="form-control" value="<%- typeof address !== 'undefined' ? address : '' %>" value="">
|
||||
</div>
|
||||
</div>
|
7
frontend/js/app/nginx/access/form/client.js
Normal file
7
frontend/js/app/nginx/access/form/client.js
Normal file
@ -0,0 +1,7 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./client.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'row'
|
||||
});
|
@ -14,6 +14,16 @@
|
||||
<td>
|
||||
<%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>
|
||||
</td>
|
||||
<td>
|
||||
<%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %>
|
||||
</td>
|
||||
<td>
|
||||
<% if (satify_any) { %>
|
||||
<%- i18n('str', 'any') %>
|
||||
<%} else { %>
|
||||
<%- i18n('str', 'all') %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %>
|
||||
</td>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<thead>
|
||||
<th width="30"> </th>
|
||||
<th><%- i18n('str', 'name') %></th>
|
||||
<th><%- i18n('users', 'title') %></th>
|
||||
<th><%- i18n('access-lists', 'authorization') %></th>
|
||||
<th><%- i18n('access-lists', 'access') %></th>
|
||||
<th><%- i18n('access-lists', 'satisfy') %></th>
|
||||
<th><%- i18n('proxy-hosts', 'title') %></th>
|
||||
<% if (canManage) { %>
|
||||
<th> </th>
|
||||
|
@ -40,7 +40,7 @@ module.exports = Mn.View.extend({
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
App.Api.Nginx.AccessLists.getAll(['owner', 'items'])
|
||||
App.Api.Nginx.AccessLists.getAll(['owner', 'items', 'clients'])
|
||||
.then(response => {
|
||||
if (!view.isDestroyed()) {
|
||||
if (response && response.length) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="title">
|
||||
<i class="fe fe-lock text-teal"></i> <%- name %>
|
||||
</div>
|
||||
<span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span>
|
||||
<span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span>
|
||||
<% } else { %>
|
||||
<div class="title">
|
||||
<i class="fe fe-unlock text-yellow"></i> <%- i18n('access-lists', 'public') %>
|
||||
|
@ -222,7 +222,7 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
},
|
||||
load: function (query, callback) {
|
||||
App.Api.Nginx.AccessLists.getAll(['items'])
|
||||
App.Api.Nginx.AccessLists.getAll(['items', 'clients'])
|
||||
.then(rows => {
|
||||
callback(rows);
|
||||
})
|
||||
|
@ -33,7 +33,9 @@
|
||||
"unknown": "Unknown",
|
||||
"expires": "Expires",
|
||||
"value": "Value",
|
||||
"please-wait": "Please wait..."
|
||||
"please-wait": "Please wait...",
|
||||
"all": "All",
|
||||
"any": "Any"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login to your account"
|
||||
@ -184,10 +186,16 @@
|
||||
"public": "Publicly Accessible",
|
||||
"public-sub": "No Access Restrictions",
|
||||
"help-title": "What is an Access List?",
|
||||
"help-content": "Access Lists provide authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in.",
|
||||
"help-content": "Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple client rules, usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in or that you want to protect from access by unknown clients.",
|
||||
"item-count": "{count} {count, select, 1{User} other{Users}}",
|
||||
"client-count": "{count} {count, select, 1{Rule} other{Rules}}",
|
||||
"proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}",
|
||||
"delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion."
|
||||
"delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion.",
|
||||
"details": "Details",
|
||||
"authorization": "Authorization",
|
||||
"access": "Access",
|
||||
"satisfy": "Satisfy",
|
||||
"satisfy-any": "Satify Any"
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
|
@ -10,6 +10,7 @@ const model = Backbone.Model.extend({
|
||||
modified_on: null,
|
||||
name: '',
|
||||
items: [],
|
||||
clients: [],
|
||||
// The following are expansions:
|
||||
owner: null
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user