diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index 0f8eab6d..1304b521 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -28,7 +28,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ # Install the package in editable mode with dev dependencies
+ pip install -e .[dev]
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
diff --git a/README.md b/README.md
index f6f2ca12..ca3cbcc3 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,7 @@ The REST API is documented using Swagger (OpenAPI). After installing and running
## Change Log
+- 1.1.2 - minor security updates (removed unused JS files), setup.py now reads dependencies from requirements.txt
- 1.1.1 - Machine API Key rewrited.
- API keys for machines are now tied to one of the existing users. If there is a need to have API access for machine, first create service user, and set the access rights. Then create machine key as Admin and assign it to this user.
- 1.1.0 - Major Architecture Refactoring and Whitelist Integration
diff --git a/flowapp/__about__.py b/flowapp/__about__.py
index c7f183a6..bf1e7f93 100755
--- a/flowapp/__about__.py
+++ b/flowapp/__about__.py
@@ -1 +1 @@
-__version__ = "1.1.1"
+__version__ = "1.1.2"
diff --git a/flowapp/models/utils.py b/flowapp/models/utils.py
index d0bf1208..a5e38aae 100644
--- a/flowapp/models/utils.py
+++ b/flowapp/models/utils.py
@@ -22,7 +22,8 @@ def check_rule_limit(org_id: int, rule_type: RuleTypes) -> bool:
:param rule_type: RuleType rule type
:return: boolean
"""
- flowspec_limit = current_app.config.get("FLOWSPEC_MAX_RULES", 9000)
+ flowspec4_limit = current_app.config.get("FLOWSPEC4_MAX_RULES", 9000)
+ flowspec6_limit = current_app.config.get("FLOWSPEC6_MAX_RULES", 9000)
rtbh_limit = current_app.config.get("RTBH_MAX_RULES", 100000)
fs4 = db.session.query(Flowspec4).filter_by(rstate_id=1).count()
fs6 = db.session.query(Flowspec6).filter_by(rstate_id=1).count()
@@ -32,10 +33,10 @@ def check_rule_limit(org_id: int, rule_type: RuleTypes) -> bool:
org = Organization.query.filter_by(id=org_id).first()
if rule_type == RuleTypes.IPv4 and org.limit_flowspec4 > 0:
count = db.session.query(Flowspec4).filter_by(org_id=org_id, rstate_id=1).count()
- return count >= org.limit_flowspec4 or fs4 >= flowspec_limit
+ return count >= org.limit_flowspec4 or fs4 >= flowspec4_limit
if rule_type == RuleTypes.IPv6 and org.limit_flowspec6 > 0:
count = db.session.query(Flowspec6).filter_by(org_id=org_id, rstate_id=1).count()
- return count >= org.limit_flowspec6 or fs6 >= flowspec_limit
+ return count >= org.limit_flowspec6 or fs6 >= flowspec6_limit
if rule_type == RuleTypes.RTBH and org.limit_rtbh > 0:
count = db.session.query(RTBH).filter_by(org_id=org_id, rstate_id=1).count()
return count >= org.limit_rtbh or rtbh >= rtbh_limit
@@ -244,13 +245,11 @@ def get_user_actions(user_roles):
Return list of actions based on current user role
"""
max_role = max(user_roles)
- print(max_role)
if max_role == 3:
actions = db.session.query(Action).order_by("id").all()
else:
actions = db.session.query(Action).filter_by(role_id=max_role).order_by("id").all()
result = [(g.id, g.name) for g in actions]
- print(actions, result)
return result
diff --git a/flowapp/static/js/check_all.js b/flowapp/static/js/check_all.js
deleted file mode 100644
index 5df58fc8..00000000
--- a/flowapp/static/js/check_all.js
+++ /dev/null
@@ -1,15 +0,0 @@
-document.getElementById("check-all").addEventListener("click", function(event){
- /**
- * find all checkboxes in current dashboard and toggle checked all / none
- */
- const inputs = document.querySelectorAll("input[type='checkbox']");
- if (this.checked) {
- for(let minput of inputs) {
- minput.checked = true;
- }
- } else {
- for(let minput of inputs) {
- minput.checked = false;
- }
- }
-});
\ No newline at end of file
diff --git a/flowapp/static/js/table.js b/flowapp/static/js/table.js
deleted file mode 100644
index c3d603f2..00000000
--- a/flowapp/static/js/table.js
+++ /dev/null
@@ -1,580 +0,0 @@
-'use strict';
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
-function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } }
-
-function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
-
-function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-
-function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
-
-function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
-
-function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
-
-function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
-
-function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
-
-function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
-
-function sortIp(a, b) {
- a = a.split('/');
- b = b.split('/');
- var num1 = Number(a[0].split(".").map(function (num) {
- return "000".concat(num).slice(-3);
- }).join(""));
- var num2 = Number(b[0].split(".").map(function (num) {
- return "000".concat(num).slice(-3);
- }).join(""));
- return num1 - num2;
-}
-
-function sortString(a, b) {
- var nameA = a.toLowerCase(); // ignore upper and lowercase
-
- var nameB = b.toLowerCase(); // ignore upper and lowercase
-
- if (nameA < nameB) {
- return -1;
- }
-
- if (nameA > nameB) {
- return 1;
- } // must be equal
-
-
- return 0;
-}
-
-function sortExpires(a, b) {
- var dateA = Date.parse(a);
- var dateB = Date.parse(b);
-
- if (dateA < dateB) {
- return -1;
- }
-
- if (dateA > dateB) {
- return 1;
- } // must be equal
-
-
- return 0;
-}
-
-var Button =
-/*#__PURE__*/
-function (_React$Component) {
- _inherits(Button, _React$Component);
-
- function Button() {
- _classCallCheck(this, Button);
-
- return _possibleConstructorReturn(this, _getPrototypeOf(Button).apply(this, arguments));
- }
-
- _createClass(Button, [{
- key: "render",
- value: function render() {
- var link = this.props.link;
- var css = "btn btn-" + this.props.css + " btn-sm";
- var icon = "glyphicon glyphicon-" + this.props.icon;
- return React.createElement("a", {
- className: css,
- href: link,
- role: "button"
- }, React.createElement("span", {
- className: icon
- }));
- }
- }]);
-
- return Button;
-}(React.Component);
-
-var TooltipButton =
-/*#__PURE__*/
-function (_React$Component2) {
- _inherits(TooltipButton, _React$Component2);
-
- function TooltipButton() {
- _classCallCheck(this, TooltipButton);
-
- return _possibleConstructorReturn(this, _getPrototypeOf(TooltipButton).apply(this, arguments));
- }
-
- _createClass(TooltipButton, [{
- key: "render",
- value: function render() {
- var text = this.props.text;
- return React.createElement("button", {
- type: "button",
- className: "btn btn-info btn-sm",
- "data-toggle": "tooltip",
- "data-placement": "top",
- title: text
- }, React.createElement("span", {
- className: "glyphicon glyphicon-comment"
- }));
- }
- }]);
-
- return TooltipButton;
-}(React.Component);
-
-var RulesRow =
-/*#__PURE__*/
-function (_React$Component3) {
- _inherits(RulesRow, _React$Component3);
-
- function RulesRow() {
- _classCallCheck(this, RulesRow);
-
- return _possibleConstructorReturn(this, _getPrototypeOf(RulesRow).apply(this, arguments));
- }
-
- _createClass(RulesRow, [{
- key: "render",
- value: function render() {
- var rule = this.props.rule;
- var delete_link = rule.delete_link + '/active/source';
- var time_link = rule.time_link + '/active/source';
-
- if (this.props.rstate && this.props.sortKey && this.props.filterText) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- } else if (this.props.rstate && this.props.sortKey) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- } else if (this.props.rstate) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/source';
- time_link = rule.time_link + '/' + this.props.rstate + '/source';
- }
-
- var combutton = '';
-
- if (rule.comment) {
- combutton = React.createElement(TooltipButton, {
- text: rule.comment
- });
- }
-
- var trClass = Date.parse(rule.expires) < Date.now() ? 'warning' : '';
- return React.createElement("tr", {
- className: trClass
- }, React.createElement("td", null, rule.source), React.createElement("td", null, rule.source_port), React.createElement("td", null, rule.dest), React.createElement("td", null, rule.dest_port), React.createElement("td", null, rule.protocol), React.createElement("td", null, rule.expires), React.createElement("td", null, rule.action), React.createElement("td", null, rule.flags), React.createElement("td", null, rule.user), React.createElement("td", null, React.createElement(Button, {
- link: time_link,
- css: "primary",
- icon: "time"
- }), "\xA0", React.createElement(Button, {
- link: delete_link,
- css: "danger",
- icon: "remove"
- }), "\xA0", combutton));
- }
- }]);
-
- return RulesRow;
-}(React.Component);
-
-var RtbhRow =
-/*#__PURE__*/
-function (_React$Component4) {
- _inherits(RtbhRow, _React$Component4);
-
- function RtbhRow() {
- _classCallCheck(this, RtbhRow);
-
- return _possibleConstructorReturn(this, _getPrototypeOf(RtbhRow).apply(this, arguments));
- }
-
- _createClass(RtbhRow, [{
- key: "render",
- value: function render() {
- var rule = this.props.rule;
- var trClass = Date.parse(rule.expires) < Date.now() ? 'warning' : '';
- var delete_link = rule.delete_link + '/active/source';
- var time_link = rule.time_link + '/active/source';
-
- if (this.props.rstate && this.props.sortKey && this.props.filterText) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- } else if (this.props.rstate && this.props.sortKey) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- } else if (this.props.rstate) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/source';
- time_link = rule.time_link + '/' + this.props.rstate + '/source';
- }
-
- var combutton = '';
-
- if (rule.comment) {
- combutton = React.createElement(TooltipButton, {
- text: rule.comment
- });
- }
-
- return React.createElement("tr", {
- className: trClass
- }, React.createElement("td", null, rule.ipv4, " ", rule.ipv6), React.createElement("td", null, rule.community), React.createElement("td", null, rule.expires), React.createElement("td", null, rule.user), React.createElement("td", null, React.createElement(Button, {
- link: time_link,
- css: "primary",
- icon: "time"
- }), "\xA0", React.createElement(Button, {
- link: delete_link,
- css: "danger",
- icon: "remove"
- }), "\xA0", combutton));
- }
- }]);
-
- return RtbhRow;
-}(React.Component);
-
-var RulesTable =
-/*#__PURE__*/
-function (_React$Component5) {
- _inherits(RulesTable, _React$Component5);
-
- function RulesTable(props) {
- var _this;
-
- _classCallCheck(this, RulesTable);
-
- _this = _possibleConstructorReturn(this, _getPrototypeOf(RulesTable).call(this, props));
-
- _defineProperty(_assertThisInitialized(_this), "onSort", function (column) {
- return function (e) {
- var direction = _this.state.sort.column ? _this.state.sort.direction === 'asc' ? 'desc' : 'asc' : 'desc';
- var sort = {
- 'column': column,
- 'direction': direction
- };
-
- _this.props.onSortKeyChange(sort);
-
- _this.setState({
- sort: {
- column: column,
- direction: direction
- }
- });
- };
- });
-
- _defineProperty(_assertThisInitialized(_this), "setArrow", function (column) {
- var className = 'sort-direction';
-
- if (_this.state.sort.column === column) {
- className += _this.state.sort.direction === 'asc' ? ' asc' : ' desc';
- }
-
- return className;
- });
-
- _this.state = {
- sort: {
- column: null,
- direction: 'desc'
- }
- };
- return _this;
- }
-
- _createClass(RulesTable, [{
- key: "render",
- value: function render() {
- var cels = [];
- var columns = this.props.columns;
-
- for (var col in columns) {
- cels.push(React.createElement("th", {
- key: col,
- onClick: this.onSort(col)
- }, columns[col], React.createElement("span", {
- className: this.setArrow(col)
- })));
- }
-
- return React.createElement("table", {
- className: "table table-hover",
- id: this.props.cssId
- }, React.createElement("thead", null, React.createElement("tr", null, cels, React.createElement("th", null, "Action"))), React.createElement("tbody", null, this.props.rows));
- }
- }]);
-
- return RulesTable;
-}(React.Component);
-
-var TablesContainer =
-/*#__PURE__*/
-function (_React$Component6) {
- _inherits(TablesContainer, _React$Component6);
-
- function TablesContainer(props) {
- var _this2;
-
- _classCallCheck(this, TablesContainer);
-
- _this2 = _possibleConstructorReturn(this, _getPrototypeOf(TablesContainer).call(this, props));
-
- _defineProperty(_assertThisInitialized(_this2), "rulesColumns", {
- 'source': 'Source address',
- 'source_port': 'Source port',
- 'dest': 'Dest. address',
- 'dest_port': 'Dest. port',
- 'protocol': 'Protocol',
- 'expires': 'Expires',
- 'action': 'Action',
- 'flags': 'Flags',
- 'user': 'User'
- });
-
- _defineProperty(_assertThisInitialized(_this2), "rtbhColumns", {
- 'ipv4': 'IP adress (v4 or v6)',
- 'community': 'Community',
- 'expires': 'Expires',
- 'user': 'User'
- });
-
- _this2.state = {
- sortKey: _this2.props.sortKey,
- sortDirection: 'asc',
- sortKeyRtbh: _this2.props.sortKey,
- sortDirectionRthb: 'asc'
- };
- _this2.handleSortKeyChange = _this2.handleSortKeyChange.bind(_assertThisInitialized(_this2));
- _this2.handleSortKeyRtbhChange = _this2.handleSortKeyRtbhChange.bind(_assertThisInitialized(_this2));
- return _this2;
- }
-
- _createClass(TablesContainer, [{
- key: "handleSortKeyChange",
- value: function handleSortKeyChange(sort) {
- this.setState({
- sortKey: sort['column'],
- sortDirection: sort['direction']
- });
- }
- }, {
- key: "handleSortKeyRtbhChange",
- value: function handleSortKeyRtbhChange(sort) {
- this.setState({
- sortKeyRtbh: sort['column'],
- sortDirectionRtbh: sort['direction']
- });
- }
- }, {
- key: "render",
- value: function render() {
- var _this3 = this;
-
- var filterText = this.props.filterText.toLowerCase();
- var rules_rows = [];
- var rtbh_rows = [];
- var column = Object.keys(this.rulesColumns).indexOf(this.state.sortKey) > -1 ? this.state.sortKey : 'source';
- var columnRtbh = Object.keys(this.rtbhColumns).indexOf(this.state.sortKeyRtbh) > -1 ? this.state.sortKeyRtbh : 'ipv4'; //filter out the rules
- // this.props.rtbh.filter((rule) => rule.fulltext.indexOf(filterText) > -1)
-
- var sortedRules = this.props.rules.filter(function (rule) {
- return rule.fulltext.indexOf(filterText) > -1;
- }).sort(function (a, b) {
- if (column === 'source' || column === 'dest') {
- return sortIp(a[column], b[column]);
- } else if (column === 'expires') {
- return sortExpires(a[column], b[column]);
- } else {
- return sortString(a[column], b[column]);
- }
- });
- var sortedRtbh = this.props.rtbh.filter(function (rule) {
- return rule.fulltext.indexOf(filterText) > -1;
- }).sort(function (a, b) {
- if (columnRtbh === 'ipv4') {
- return sortIp(a[columnRtbh], b[columnRtbh]);
- } else if (columnRtbh === 'expires') {
- return sortExpires(a[columnRtbh], b[columnRtbh]);
- } else {
- return sortString(a[(columnRtbh, b[columnRtbh])]);
- }
- });
-
- if (this.state.sortDirection === 'desc') {
- sortedRules = sortedRules.reverse();
- }
-
- if (this.state.sortDirectionRtbh === 'desc') {
- sortedRtbh = sortedRtbh.reverse();
- }
-
- sortedRules.forEach(function (rule) {
- rules_rows.push(React.createElement(RulesRow, {
- sortKey: _this3.state.sortKey,
- filterText: filterText,
- rstate: _this3.props.rstate,
- rule: rule,
- key: rule.fulltext
- }));
- });
- sortedRtbh.forEach(function (rule) {
- rtbh_rows.push(React.createElement(RtbhRow, {
- sortKey: _this3.state.sortKeyRtbh,
- filterText: filterText,
- rstate: _this3.props.rstate,
- rule: rule,
- key: rule.fulltext
- }));
- });
- return React.createElement("div", null, React.createElement("h2", null, this.props.title, " IPv4/IPv6 rules"), React.createElement(RulesTable, {
- cssId: "ip-table",
- columns: this.rulesColumns,
- rows: rules_rows,
- onSortKeyChange: this.handleSortKeyChange
- }), React.createElement("h2", null, this.props.title, " RTBH rules"), React.createElement(RulesTable, {
- cssId: "rtbh-table",
- columns: this.rtbhColumns,
- rows: rtbh_rows,
- onSortKeyChange: this.handleSortKeyRtbhChange
- }));
- }
- }]);
-
- return TablesContainer;
-}(React.Component);
-
-var SearchBar =
-/*#__PURE__*/
-function (_React$Component7) {
- _inherits(SearchBar, _React$Component7);
-
- function SearchBar(props) {
- var _this4;
-
- _classCallCheck(this, SearchBar);
-
- _this4 = _possibleConstructorReturn(this, _getPrototypeOf(SearchBar).call(this, props));
- _this4.handleFilterTextChange = _this4.handleFilterTextChange.bind(_assertThisInitialized(_this4));
- return _this4;
- }
-
- _createClass(SearchBar, [{
- key: "handleFilterTextChange",
- value: function handleFilterTextChange(e) {
- this.props.onFilterTextChange(e.target.value);
- }
- }, {
- key: "render",
- value: function render() {
- return React.createElement("form", {
- className: "navbar-form pull-right",
- role: "search"
- }, React.createElement("div", {
- className: "input-group"
- }, React.createElement("input", {
- className: "form-control",
- type: "text",
- placeholder: "Search...",
- value: this.props.filterText,
- onChange: this.handleFilterTextChange
- }), React.createElement("div", {
- className: "input-group-btn"
- }, React.createElement("button", {
- className: "btn btn-default"
- }, React.createElement("i", {
- className: "glyphicon glyphicon-search"
- })))));
- }
- }]);
-
- return SearchBar;
-}(React.Component);
-
-var FilterableTablesContainer =
-/*#__PURE__*/
-function (_React$Component8) {
- _inherits(FilterableTablesContainer, _React$Component8);
-
- function FilterableTablesContainer(props) {
- var _this5;
-
- _classCallCheck(this, FilterableTablesContainer);
-
- _this5 = _possibleConstructorReturn(this, _getPrototypeOf(FilterableTablesContainer).call(this, props));
- _this5.state = {
- filterText: _this5.props.filterStart
- };
- _this5.handleFilterTextChange = _this5.handleFilterTextChange.bind(_assertThisInitialized(_this5));
- return _this5;
- }
-
- _createClass(FilterableTablesContainer, [{
- key: "handleFilterTextChange",
- value: function handleFilterTextChange(filterText) {
- this.setState({
- filterText: filterText
- });
- }
- }, {
- key: "render",
- value: function render() {
- var cssActive = this.props.rstate === 'active' ? "active nav-item" : "nav-item";
- var cssExpired = this.props.rstate === 'expired' ? "active nav-item" : "nav-item";
- var cssAll = this.props.rstate === 'all' ? "active nav-item" : "nav-item";
- return React.createElement("div", {
- className: "row"
- }, React.createElement("div", {
- className: "container",
- id: "dashboard-nav"
- }, React.createElement("div", {
- className: "col-md-6"
- }, React.createElement("h1", null, "Default dashboard")), React.createElement("div", {
- className: "col-md-6"
- }, React.createElement("ul", {
- className: "nav nav-pills pull-right"
- }, React.createElement("li", null, React.createElement(SearchBar, {
- filterText: this.state.filterText,
- onFilterTextChange: this.handleFilterTextChange
- })), React.createElement("li", {
- className: cssActive
- }, React.createElement("a", {
- className: "nav-link",
- href: "/show/active/"
- }, "Active")), React.createElement("li", {
- className: cssExpired
- }, React.createElement("a", {
- className: "nav-link",
- href: "/show/expired/"
- }, "Expired")), React.createElement("li", {
- className: cssAll
- }, React.createElement("a", {
- className: "nav-link",
- href: "/show/all/"
- }, "All"))))), React.createElement(TablesContainer, {
- rules: this.props.rules,
- rtbh: this.props.rtbh,
- title: this.props.title,
- rstate: this.props.rstate,
- sortKey: this.props.sortKey,
- filterText: this.state.filterText
- }));
- }
- }]);
-
- return FilterableTablesContainer;
-}(React.Component);
-
-var domContainer = document.querySelector('#rules_table_container');
-ReactDOM.render(React.createElement(FilterableTablesContainer, {
- rules: RULES,
- rtbh: RTBH,
- title: TITLE,
- filterStart: FILTER_START,
- rstate: RSTATE,
- sortKey: SORT_KEY
-}), domContainer);
\ No newline at end of file
diff --git a/flowapp/static/js/table.jsx b/flowapp/static/js/table.jsx
deleted file mode 100644
index 172326a9..00000000
--- a/flowapp/static/js/table.jsx
+++ /dev/null
@@ -1,496 +0,0 @@
-'use strict';
-
-
-function sortIp(a, b) {
- a = a.split('/');
- b = b.split('/');
- const num1 = Number(a[0].split(".").map((num) => (`000${num}`).slice(-3) ).join(""));
- const num2 = Number(b[0].split(".").map((num) => (`000${num}`).slice(-3) ).join(""));
- return num1-num2;
-}
-
-function sortString(a, b) {
- const nameA = a.toLowerCase(); // ignore upper and lowercase
- const nameB = b.toLowerCase(); // ignore upper and lowercase
-
- if (nameA < nameB) {
- return -1;
- }
- if (nameA > nameB) {
- return 1;
- }
-
- // must be equal
- return 0;
-}
-
-function sortExpires(a, b) {
- const dateA = Date.parse(a);
- const dateB = Date.parse(b);
-
- if (dateA < dateB) {
- return -1;
- }
- if (dateA > dateB) {
- return 1;
- }
-
- // must be equal
- return 0;
-
-}
-
-
-class Button extends React.Component {
-
- render() {
- const link = this.props.link;
- const css = "btn btn-" + this.props.css + " btn-sm";
- const icon = "glyphicon glyphicon-" + this.props.icon;
-
- return (
-
-
-
- );
-
- }
-
-}
-
-class TooltipButton extends React.Component {
-
- render() {
- const text = this.props.text;
-
- return (
-
- )
-
- }
-}
-
-
-class RulesRow extends React.Component {
-
-
- render() {
- const rule = this.props.rule;
-
- let delete_link = rule.delete_link + '/active/source';
- let time_link = rule.time_link + '/active/source';
-
- if (this.props.rstate && this.props.sortKey && this.props.filterText) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- }
- else if (this.props.rstate && this.props.sortKey) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- }
- else if (this.props.rstate) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/source';
- time_link = rule.time_link + '/' + this.props.rstate + '/source';
- }
-
-
- let combutton = '';
-
- if (rule.comment) {
- combutton = ;
- }
-
- const trClass = Date.parse(rule.expires) < Date.now() ? 'warning' : '';
-
- return (
-
- | {rule.source} |
- {rule.source_port} |
- {rule.dest} |
- {rule.dest_port} |
- {rule.protocol} |
- {rule.expires} |
- {rule.action} |
- {rule.flags} |
- {rule.user} |
-
-
-
-
-
- {combutton}
- |
-
- );
- }
-}
-
-
-class RtbhRow extends React.Component {
-
-
- render() {
-
-
- const rule = this.props.rule;
- const trClass = Date.parse(rule.expires) < Date.now() ? 'warning' : '';
-
- let delete_link = rule.delete_link + '/active/source';
- let time_link = rule.time_link + '/active/source';
-
- if (this.props.rstate && this.props.sortKey && this.props.filterText) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey + '/' + this.props.filterText;
- }
- else if (this.props.rstate && this.props.sortKey) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- time_link = rule.time_link + '/' + this.props.rstate + '/' + this.props.sortKey;
- }
- else if (this.props.rstate) {
- delete_link = rule.delete_link + '/' + this.props.rstate + '/source';
- time_link = rule.time_link + '/' + this.props.rstate + '/source';
- }
-
-
-
- let combutton = '';
- if (rule.comment) {
- combutton = ;
- }
-
- return (
-
- | {rule.ipv4} {rule.ipv6} |
- {rule.community} |
- {rule.expires} |
- {rule.user} |
-
-
-
-
-
- {combutton}
- |
-
- );
- }
-}
-
-
-
-class RulesTable extends React.Component {
-
- constructor(props) {
- super(props);
- this.state = {
- sort: {
- column: null,
- direction: 'desc',
- }
- };
- }
-
-
- onSort = (column) => (e) => {
- const direction = this.state.sort.column ? (this.state.sort.direction === 'asc' ? 'desc' : 'asc') : 'desc';
-
- const sort = {
- 'column': column,
- 'direction': direction
- };
-
- this.props.onSortKeyChange(sort);
-
- this.setState({
- sort: {
- column,
- direction,
- }
- });
-
- };
-
- setArrow = (column) => {
- let className = 'sort-direction';
-
- if (this.state.sort.column === column) {
- className += this.state.sort.direction === 'asc' ? ' asc' : ' desc';
- }
-
- return className;
- };
-
-
-
- render() {
-
- let cels = [];
-
- const columns = this.props.columns;
-
- for (let col in columns) {
- cels.push(
- {columns[col]}
-
- | )
- }
-
-
- return (
-
-
-
- {cels}
- | Action |
-
-
- {this.props.rows}
-
- );
-
- }
-}
-
-
-class TablesContainer extends React.Component {
-
- constructor(props) {
- super(props);
- this.state = {
- sortKey: this.props.sortKey,
- sortDirection: 'asc',
- sortKeyRtbh: this.props.sortKey,
- sortDirectionRthb: 'asc'
- };
-
- this.handleSortKeyChange = this.handleSortKeyChange.bind(this);
- this.handleSortKeyRtbhChange = this.handleSortKeyRtbhChange.bind(this);
-
- }
-
- handleSortKeyChange(sort) {
- this.setState({
- sortKey: sort['column'],
- sortDirection: sort['direction']
- });
- }
-
- handleSortKeyRtbhChange(sort) {
- this.setState({
- sortKeyRtbh: sort['column'],
- sortDirectionRtbh: sort['direction']
- });
- }
-
- rulesColumns = {
- 'source': 'Source address',
- 'source_port': 'Source port',
- 'dest': 'Dest. address',
- 'dest_port': 'Dest. port',
- 'protocol': 'Protocol',
- 'expires': 'Expires',
- 'action': 'Action',
- 'flags': 'Flags',
- 'user': 'User'
- };
-
- rtbhColumns = {
- 'ipv4': 'IP adress (v4 or v6)',
- 'community': 'Community',
- 'expires': 'Expires',
- 'user': 'User'
- };
-
-
- render() {
- const filterText = this.props.filterText.toLowerCase();
- const rules_rows = [];
- const rtbh_rows = [];
- const column = Object.keys(this.rulesColumns).indexOf(this.state.sortKey) > -1 ? this.state.sortKey : 'source';
- const columnRtbh = Object.keys(this.rtbhColumns).indexOf(this.state.sortKeyRtbh) > -1 ? this.state.sortKeyRtbh : 'ipv4';
-
-
-
- //filter out the rules
- // this.props.rtbh.filter((rule) => rule.fulltext.indexOf(filterText) > -1)
-
- let sortedRules = this.props.rules.filter((rule) => rule.fulltext.indexOf(filterText) > -1).sort((a, b) => {
- if (column === 'source' || column === 'dest') {
- return sortIp(a[column], b[column]);
- }
- else if (column === 'expires') {
- return sortExpires(a[column], b[column]);
- }
- else {
- return sortString(a[column], b[column])
- }
- });
-
- let sortedRtbh = this.props.rtbh.filter((rule) => rule.fulltext.indexOf(filterText) > -1).sort((a, b) => {
- if (columnRtbh === 'ipv4') {
- return sortIp(a[columnRtbh], b[columnRtbh]);
- }
- else if (columnRtbh === 'expires') {
- return sortExpires(a[columnRtbh], b[columnRtbh]);
- }
- else {
- return sortString(a[columnRtbh, b[columnRtbh]]);
- }
- });
-
- if (this.state.sortDirection === 'desc') {
- sortedRules = sortedRules.reverse();
- }
-
- if (this.state.sortDirectionRtbh === 'desc') {
- sortedRtbh = sortedRtbh.reverse();
- }
-
- sortedRules.forEach((rule) => {
- rules_rows.push(
-
- );
- });
-
- sortedRtbh.forEach((rule) => {
- rtbh_rows.push(
-
- );
- });
-
- return (
-
-
{this.props.title} IPv4/IPv6 rules
-
- {this.props.title} RTBH rules
-
-
- );
- }
-}
-
-
-
-
-class SearchBar extends React.Component {
- constructor(props) {
- super(props);
- this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
- }
-
- handleFilterTextChange(e) {
- this.props.onFilterTextChange(e.target.value);
- }
-
- render() {
- return (
-
-
- );
- }
-}
-
-class FilterableTablesContainer extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- filterText: this.props.filterStart
- };
-
- this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
- }
-
- handleFilterTextChange(filterText) {
- this.setState({
- filterText: filterText
- });
- }
-
- render() {
-
- const cssActive = this.props.rstate === 'active' ? "active nav-item" : "nav-item";
- const cssExpired = this.props.rstate === 'expired' ? "active nav-item" : "nav-item";
- const cssAll = this.props.rstate === 'all' ? "active nav-item" : "nav-item";
-
- return (
-
-
-
-
Default dashboard
-
-
-
-
-
-
- );
- }
-}
-
-
-
-const domContainer = document.querySelector('#rules_table_container');
-ReactDOM.render(, domContainer);
\ No newline at end of file
diff --git a/flowapp/templates/layouts/default.html b/flowapp/templates/layouts/default.html
index c6f6e4fe..53a23a5c 100644
--- a/flowapp/templates/layouts/default.html
+++ b/flowapp/templates/layouts/default.html
@@ -13,11 +13,7 @@
{% block title %}{% endblock %}
-
+
diff --git a/flowapp/templates/pages/orgs.html b/flowapp/templates/pages/orgs.html
index 3d2e3df0..50ed1a16 100644
--- a/flowapp/templates/pages/orgs.html
+++ b/flowapp/templates/pages/orgs.html
@@ -9,8 +9,8 @@
| {{ rtbh_all_count }} / {{ rtbh_limit }} |
- {{ flowspec4_all_count }} / {{ flowspec_limit }} |
- {{ flowspec6_all_count }} / {{ flowspec_limit }} |
+ {{ flowspec4_all_count }} / {{ flowspec4_limit }} |
+ {{ flowspec6_all_count }} / {{ flowspec6_limit }} |
diff --git a/flowapp/utils/app_factory.py b/flowapp/utils/app_factory.py
index 234522fd..d8bd30e1 100644
--- a/flowapp/utils/app_factory.py
+++ b/flowapp/utils/app_factory.py
@@ -217,5 +217,4 @@ def _register_user_to_session(uuid, db):
roles = [i > 1 for i in session["user_role_ids"]]
session["can_edit"] = True if all(roles) and roles else []
# check if user has multiple organizations and return True if so
- print(f"DEBUG SESSION {session}")
return user, len(user.organization.all()) > 1
diff --git a/flowapp/views/admin.py b/flowapp/views/admin.py
index 7912e86a..5cc7309e 100644
--- a/flowapp/views/admin.py
+++ b/flowapp/views/admin.py
@@ -311,8 +311,6 @@ def organizations():
flowspec4_all_count = db.session.query(Flowspec4).filter(Flowspec4.rstate_id == 1).count()
flowspec6_all_count = db.session.query(Flowspec6).filter(Flowspec6.rstate_id == 1).count()
rtbh_all_count = db.session.query(RTBH).filter(RTBH.rstate_id == 1).count()
- flowspec_limit = current_app.config.get("FLOWSPEC_MAX_RULES", 9000)
- rtbh_limit = current_app.config.get("RTBH_MAX_RULES", 100000)
# Convert query result to a dictionary {org_id: count}
rtbh_counts = {org_id: count for org_id, count in rtbh_counts_query}
@@ -328,8 +326,9 @@ def organizations():
rtbh_all_count=rtbh_all_count,
flowspec4_all_count=flowspec4_all_count,
flowspec6_all_count=flowspec6_all_count,
- flowspec_limit=flowspec_limit,
- rtbh_limit=rtbh_limit,
+ flowspec4_limit=current_app.config.get("FLOWSPEC4_MAX_RULES", 9000),
+ flowspec6_limit=current_app.config.get("FLOWSPEC6_MAX_RULES", 9000),
+ rtbh_limit=current_app.config.get("RTBH_MAX_RULES", 100000),
)
diff --git a/flowapp/views/api_common.py b/flowapp/views/api_common.py
index 3951b093..2aaef731 100644
--- a/flowapp/views/api_common.py
+++ b/flowapp/views/api_common.py
@@ -1,9 +1,10 @@
import jwt
import ipaddress
+from typing import Dict, Any, List, Tuple, Optional, Union, Callable
+from datetime import datetime, timedelta
-from flask import request, jsonify, current_app
+from flask import request, jsonify, current_app, Response
from functools import wraps
-from datetime import datetime, timedelta
from flowapp.constants import RULE_NAMES_DICT, WITHDRAW, TIME_FORMAT_ARG, RuleTypes
from flowapp.models import (
@@ -33,10 +34,10 @@
from flowapp import db, validators, flowspec, messages
-def token_required(f):
+def token_required(f: Callable) -> Callable:
@wraps(f)
- def decorated(*args, **kwargs):
- token = None
+ def decorated(*args: Any, **kwargs: Any) -> Union[Response, Tuple[Response, int]]:
+ token: Optional[str] = None
if "x-access-token" in request.headers:
token = request.headers["x-access-token"]
@@ -45,8 +46,8 @@ def decorated(*args, **kwargs):
return jsonify({"message": "auth token is missing"}), 401
try:
- data = jwt.decode(token, current_app.config.get("JWT_SECRET"), algorithms=["HS256"])
- current_user = data["user"]
+ data: Dict[str, Any] = jwt.decode(token, current_app.config.get("JWT_SECRET"), algorithms=["HS256"])
+ current_user: Dict[str, Any] = data["user"]
except jwt.DecodeError:
return jsonify({"message": "auth token is invalid"}), 403
except jwt.ExpiredSignatureError:
@@ -57,14 +58,14 @@ def decorated(*args, **kwargs):
return decorated
-def authorize(user_key):
+def authorize(user_key: str) -> Tuple[Response, int]:
"""
- Generate API Key for the loged user using PyJWT
+ Generate API Key for the logged user using PyJWT
:return: page with token
"""
- jwt_key = current_app.config.get("JWT_SECRET")
+ jwt_key: Optional[str] = current_app.config.get("JWT_SECRET")
# try normal user key first
- model = db.session.query(ApiKey).filter_by(key=user_key).first()
+ model: Optional[Union[ApiKey, MachineApiKey]] = db.session.query(ApiKey).filter_by(key=user_key).first()
# if not found try machine key
if not model:
model = db.session.query(MachineApiKey).filter_by(key=user_key).first()
@@ -78,7 +79,7 @@ def authorize(user_key):
# check if the key is not used by different machine
if model and ipaddress.ip_address(model.machine) == ipaddress.ip_address(request.remote_addr):
- payload = {
+ payload: Dict[str, Any] = {
"user": {
"uuid": model.user.uuid,
"id": model.user.id,
@@ -91,24 +92,24 @@ def authorize(user_key):
"exp": datetime.now() + timedelta(minutes=30),
}
# encoded = jwt.encode(payload, jwt_key, algorithm="HS256").decode("utf-8")
- encoded = jwt.encode(payload, jwt_key, algorithm="HS256")
+ encoded: str = jwt.encode(payload, jwt_key, algorithm="HS256")
return jsonify({"token": encoded})
else:
return jsonify({"message": f"auth token is not valid from machine {request.remote_addr}"}), 403
-def check_readonly(func):
+def check_readonly(func: Callable) -> Callable:
"""
Check if the token is readonly
Used in api endpoints
"""
@wraps(func)
- def decorated_function(*args, **kwargs):
+ def decorated_function(*args: Any, **kwargs: Any) -> Union[Response, Tuple[Response, int]]:
# Access read only flag from first of the args
- current_user = kwargs.get("current_user", False)
- read_only = current_user.get("readonly", False)
+ current_user: Dict[str, Any] = kwargs.get("current_user", {})
+ read_only: bool = current_user.get("readonly", False)
if read_only:
return jsonify({"message": "read only token can't perform this action"}), 403
return func(*args, **kwargs)
@@ -116,20 +117,20 @@ def decorated_function(*args, **kwargs):
return decorated_function
-# endpints
+# endpoints
-def index(current_user, key_map):
- prefered_tf = request.args.get(TIME_FORMAT_ARG) if request.args.get(TIME_FORMAT_ARG) else ""
+def index(current_user: Dict[str, Any], key_map: Dict[str, str]) -> Response:
+ prefered_tf: str = request.args.get(TIME_FORMAT_ARG) if request.args.get(TIME_FORMAT_ARG) else ""
- net_ranges = get_user_nets(current_user["id"])
- rules4 = db.session.query(Flowspec4).order_by(Flowspec4.expires.desc()).all()
- rules6 = db.session.query(Flowspec6).order_by(Flowspec6.expires.desc()).all()
- rules_rtbh = db.session.query(RTBH).order_by(RTBH.expires.desc()).all()
+ net_ranges: List[str] = get_user_nets(current_user["id"])
+ rules4: List[Flowspec4] = db.session.query(Flowspec4).order_by(Flowspec4.expires.desc()).all()
+ rules6: List[Flowspec6] = db.session.query(Flowspec6).order_by(Flowspec6.expires.desc()).all()
+ rules_rtbh: List[RTBH] = db.session.query(RTBH).order_by(RTBH.expires.desc()).all()
# admin can see and edit any rules
if 3 in current_user["role_ids"]:
- payload = {
+ payload: Dict[str, List[Dict[str, Any]]] = {
key_map["ipv4_rules"]: [rule.to_dict(prefered_tf) for rule in rules4],
key_map["ipv6_rules"]: [rule.to_dict(prefered_tf) for rule in rules6],
key_map["rtbh_rules"]: [rule.to_dict(prefered_tf) for rule in rules_rtbh],
@@ -141,11 +142,16 @@ def index(current_user, key_map):
rules6 = validators.filter_rules_in_network(net_ranges, rules6)
rules_rtbh = validators.filter_rtbh_rules(net_ranges, rules_rtbh)
- user_actions = get_user_actions(current_user["role_ids"])
- user_actions = [act[0] for act in user_actions]
+ user_actions: List[Tuple[int, str]] = get_user_actions(current_user["role_ids"])
+ user_actions_ids: List[int] = [act[0] for act in user_actions]
- rules4_editable, rules4_visible = flowspec.filter_rules_action(user_actions, rules4)
- rules6_editable, rules6_visible = flowspec.filter_rules_action(user_actions, rules6)
+ rules4_editable: List[Flowspec4]
+ rules4_visible: List[Flowspec4]
+ rules4_editable, rules4_visible = flowspec.filter_rules_action(user_actions_ids, rules4)
+
+ rules6_editable: List[Flowspec6]
+ rules6_visible: List[Flowspec6]
+ rules6_editable, rules6_visible = flowspec.filter_rules_action(user_actions_ids, rules6)
payload = {
key_map["ipv4_rules"]: [rule.to_dict(prefered_tf) for rule in rules4_editable],
@@ -157,43 +163,43 @@ def index(current_user, key_map):
return jsonify(payload)
-def all_actions(current_user):
+def all_actions(current_user: Dict[str, Any]) -> Tuple[Response, int]:
"""
Returns Actions allowed for current user
:param current_user:
:return: json response
"""
- actions = get_user_actions(current_user["role_ids"])
+ actions: List[Tuple[int, str]] = get_user_actions(current_user["role_ids"])
if actions:
return jsonify(actions)
else:
return jsonify({"message": "no actions for this user?"}), 404
-def all_communities(current_user):
+def all_communities(current_user: Dict[str, Any]) -> Tuple[Response, int]:
"""
- Returns RTHB communites allowed for current user
+ Returns RTBH communities allowed for current user
:param current_user:
:return: json response
"""
- coms = get_user_communities(current_user["role_ids"])
+ coms: List[Tuple[int, str]] = get_user_communities(current_user["role_ids"])
if coms:
return jsonify(coms)
else:
return jsonify({"message": "no actions for this user?"}), 404
-def limit_reached(count, rule_type, org_id):
- rule_name = RULE_NAMES_DICT[rule_type.value]
- org = db.session.get(Organization, org_id)
+def limit_reached(count: int, rule_type: RuleTypes, org_id: int) -> Tuple[Response, int]:
+ rule_name: str = RULE_NAMES_DICT[rule_type.value]
+ org: Optional[Organization] = db.session.get(Organization, org_id)
if rule_type == RuleTypes.IPv4:
- limit = org.limit_flowspec4
+ limit: int = org.limit_flowspec4
elif rule_type == RuleTypes.IPv6:
limit = org.limit_flowspec6
elif rule_type == RuleTypes.RTBH:
- limit = org.rtbh
+ limit = org.limit_rtbh
return (
jsonify({"message": f"Rule limit {limit} reached for {rule_name}, currently you have {count} active rules."}),
@@ -201,12 +207,14 @@ def limit_reached(count, rule_type, org_id):
)
-def global_limit_reached(count, rule_type):
- rule_name = RULE_NAMES_DICT[rule_type.value]
- if rule_type == RuleTypes.IPv4 or rule_type == RuleTypes.IPv6:
- limit = current_app.config.get("FLOWSPEC_MAX_RULES")
+def global_limit_reached(count: int, rule_type: RuleTypes) -> Tuple[Response, int]:
+ rule_name: str = RULE_NAMES_DICT[rule_type.value]
+ if rule_type == RuleTypes.IPv4:
+ limit: int = current_app.config.get("FLOWSPEC4_MAX_RULES", 9000)
+ elif rule_type == RuleTypes.IPv6:
+ limit = current_app.config.get("FLOWSPEC6_MAX_RULES", 9000)
elif rule_type == RuleTypes.RTBH:
- limit = current_app.config.get("RTBH_MAX_RULES")
+ limit = current_app.config.get("RTBH_MAX_RULES", 100000)
return (
jsonify(
@@ -216,35 +224,36 @@ def global_limit_reached(count, rule_type):
)
-def create_ipv4(current_user):
+def create_ipv4(current_user: Dict[str, Any]) -> Tuple[Response, int]:
"""
Api method for new IPv4 rule
- :param data: parsed json request
:param current_user: data from jwt token
:return: json response
"""
if check_global_rule_limit(RuleTypes.IPv4):
- count = db.session.query(Flowspec4).filter_by(rstate_id=1).count()
+ count: int = db.session.query(Flowspec4).filter_by(rstate_id=1).count()
return global_limit_reached(count=count, rule_type=RuleTypes.IPv4)
if check_rule_limit(current_user["org_id"], RuleTypes.IPv4):
count = db.session.query(Flowspec4).filter_by(rstate_id=1, org_id=current_user["org_id"]).count()
return limit_reached(count=count, rule_type=RuleTypes.IPv4, org_id=current_user["org_id"])
- net_ranges = get_user_nets(current_user["id"])
- json_request_data = request.get_json()
- form = IPv4Form(data=json_request_data, meta={"csrf": False})
+ net_ranges: List[str] = get_user_nets(current_user["id"])
+ json_request_data: Optional[Dict[str, Any]] = request.get_json()
+ form: IPv4Form = IPv4Form(data=json_request_data, meta={"csrf": False})
# add values to form instance
form.action.choices = get_user_actions(current_user["role_ids"])
form.net_ranges = net_ranges
# if the form is not valid, we should return 404 with errors
if not form.validate():
- form_errors = get_form_errors(form)
+ form_errors: Union[Dict[str, Any], bool] = get_form_errors(form)
if form_errors:
return jsonify(form_errors), 400
# Use the service to create/update the rule
+ model: Flowspec4
+ flash_message: str
model, flash_message = rule_service.create_or_update_ipv4_rule(
form_data=form.data,
user_id=current_user["id"],
@@ -253,37 +262,38 @@ def create_ipv4(current_user):
org_name=current_user["org"],
)
- pref_format = output_date_format(json_request_data, form.expires.pref_format)
- response = {"message": flash_message, "rule": model.to_dict(pref_format)}
+ pref_format: str = output_date_format(json_request_data, form.expires.pref_format)
+ response: Dict[str, Any] = {"message": flash_message, "rule": model.to_dict(pref_format)}
return jsonify(response), 201
-def create_ipv6(current_user):
+def create_ipv6(current_user: Dict[str, Any]) -> Tuple[Response, int]:
"""
Create new IPv6 rule
- :param data: parsed json request
:param current_user: data from jwt token
:return:
"""
if check_global_rule_limit(RuleTypes.IPv6):
- count = db.session.query(Flowspec6).filter_by(rstate_id=1).count()
+ count: int = db.session.query(Flowspec6).filter_by(rstate_id=1).count()
return global_limit_reached(count=count, rule_type=RuleTypes.IPv6)
if check_rule_limit(current_user["org_id"], RuleTypes.IPv6):
count = db.session.query(Flowspec6).filter_by(rstate_id=1, org_id=current_user["org_id"]).count()
return limit_reached(count=count, rule_type=RuleTypes.IPv6, org_id=current_user["org_id"])
- net_ranges = get_user_nets(current_user["id"])
- json_request_data = request.get_json()
- form = IPv6Form(data=json_request_data, meta={"csrf": False})
+ net_ranges: List[str] = get_user_nets(current_user["id"])
+ json_request_data: Optional[Dict[str, Any]] = request.get_json()
+ form: IPv6Form = IPv6Form(data=json_request_data, meta={"csrf": False})
form.action.choices = get_user_actions(current_user["role_ids"])
form.net_ranges = net_ranges
if not form.validate():
- form_errors = get_form_errors(form)
+ form_errors: Union[Dict[str, Any], bool] = get_form_errors(form)
if form_errors:
return jsonify(form_errors), 400
+ model: Flowspec6
+ flash_message: str
model, flash_message = rule_service.create_or_update_ipv6_rule(
form_data=form.data,
user_id=current_user["id"],
@@ -292,39 +302,41 @@ def create_ipv6(current_user):
org_name=current_user["org"],
)
- pref_format = output_date_format(json_request_data, form.expires.pref_format)
+ pref_format: str = output_date_format(json_request_data, form.expires.pref_format)
return jsonify({"message": flash_message, "rule": model.to_dict(pref_format)}), 201
-def create_rtbh(current_user):
+def create_rtbh(current_user: Dict[str, Any]) -> Tuple[Response, int]:
"""
Create new RTBH rule
"""
if check_global_rule_limit(RuleTypes.RTBH):
- count = db.session.query(RTBH).filter_by(rstate_id=1).count()
+ count: int = db.session.query(RTBH).filter_by(rstate_id=1).count()
return global_limit_reached(count=count, rule_type=RuleTypes.RTBH)
if check_rule_limit(current_user["org_id"], RuleTypes.RTBH):
count = db.session.query(RTBH).filter_by(rstate_id=1, org_id=current_user["org_id"]).count()
return limit_reached(count=count, rule_type=RuleTypes.RTBH, org_id=current_user["org_id"])
- all_com = db.session.query(Community).all()
+ all_com: List[Community] = db.session.query(Community).all()
if not all_com:
insert_initial_communities()
- net_ranges = get_user_nets(current_user["id"])
+ net_ranges: List[str] = get_user_nets(current_user["id"])
- json_request_data = request.get_json()
- form = RTBHForm(data=json_request_data, meta={"csrf": False})
+ json_request_data: Optional[Dict[str, Any]] = request.get_json()
+ form: RTBHForm = RTBHForm(data=json_request_data, meta={"csrf": False})
form.community.choices = get_user_communities(current_user["role_ids"])
form.net_ranges = net_ranges
if not form.validate():
- form_errors = get_form_errors(form)
+ form_errors: Union[Dict[str, Any], bool] = get_form_errors(form)
if form_errors:
return jsonify(form_errors), 400
+ model: RTBH
+ flash_message: List[str]
model, flash_message = rule_service.create_or_update_rtbh_rule(
form_data=form.data,
user_id=current_user["id"],
@@ -333,50 +345,52 @@ def create_rtbh(current_user):
org_name=current_user["org"],
)
- pref_format = output_date_format(json_request_data, form.expires.pref_format)
+ pref_format: str = output_date_format(json_request_data, form.expires.pref_format)
return jsonify({"message": flash_message, "rule": model.to_dict(pref_format)}), 201
-def ipv4_rule_get(current_user, rule_id):
+def ipv4_rule_get(current_user: Dict[str, Any], rule_id: int) -> Tuple[Response, int]:
"""
Return IPv4 rule
:param current_user:
:param rule_id:
:return:
"""
- model = db.session.get(Flowspec4, rule_id)
+ model: Optional[Flowspec4] = db.session.get(Flowspec4, rule_id)
return get_rule(current_user, model, rule_id)
-def ipv6_rule_get(current_user, rule_id):
+def ipv6_rule_get(current_user: Dict[str, Any], rule_id: int) -> Tuple[Response, int]:
"""
Return IPv6 rule
:param current_user:
:param rule_id:
:return:
"""
- model = db.session.get(Flowspec6, rule_id)
+ model: Optional[Flowspec6] = db.session.get(Flowspec6, rule_id)
return get_rule(current_user, model, rule_id)
-def rtbh_rule_get(current_user, rule_id):
+def rtbh_rule_get(current_user: Dict[str, Any], rule_id: int) -> Tuple[Response, int]:
"""
Return RTBH rule
:param current_user:
:param rule_id:
:return:
"""
- model = db.session.query(RTBH).get(rule_id)
+ model: Optional[RTBH] = db.session.query(RTBH).get(rule_id)
return get_rule(current_user, model, rule_id)
-def get_rule(current_user, model, rule_id):
+def get_rule(
+ current_user: Dict[str, Any], model: Optional[Union[Flowspec4, Flowspec6, RTBH]], rule_id: int
+) -> Tuple[Response, int]:
"""
Common rule getter - return ipv4 or ipv6 model data
:param model: rule model
:return: json
"""
- prefered_tf = request.args.get(TIME_FORMAT_ARG) if request.args.get(TIME_FORMAT_ARG) else ""
+ prefered_tf: str = request.args.get(TIME_FORMAT_ARG) if request.args.get(TIME_FORMAT_ARG) else ""
if model:
if check_access_rights(current_user, model.user_id):
@@ -387,7 +401,7 @@ def get_rule(current_user, model, rule_id):
return jsonify({"message": "rule {} not found ".format(rule_id)}), 404
-def delete_v4_rule(current_user, rule_id):
+def delete_v4_rule(current_user: Dict[str, Any], rule_id: int) -> Tuple[Response, int]:
"""
Delete rule with given id and type
:param rule_id: integer - rule id
@@ -397,7 +411,7 @@ def delete_v4_rule(current_user, rule_id):
return delete_rule(current_user, rule_id, model_name, route_model, RuleTypes.IPv4)
-def delete_v6_rule(current_user, rule_id):
+def delete_v6_rule(current_user: Dict[str, Any], rule_id: int) -> Tuple[Response, int]:
"""
Delete rule with given id and type
:param rule_id: integer - rule id
@@ -407,7 +421,7 @@ def delete_v6_rule(current_user, rule_id):
return delete_rule(current_user, rule_id, model_name, route_model, RuleTypes.IPv6)
-def delete_rtbh_rule(current_user, rule_id):
+def delete_rtbh_rule(current_user: Dict[str, Any], rule_id: int) -> Tuple[Response, int]:
"""
Delete rule with given id and type
:param rule_id: integer - rule id
@@ -417,7 +431,9 @@ def delete_rtbh_rule(current_user, rule_id):
return delete_rule(current_user, rule_id, model_name, route_model, RuleTypes.RTBH)
-def delete_rule(current_user, rule_id, model_name, route_model, rule_type):
+def delete_rule(
+ current_user: Dict[str, Any], rule_id: int, model_name: type, route_model: Callable, rule_type: RuleTypes
+) -> Tuple[Response, int]:
"""
Common method for deleting ipv4 or ipv6 rules
:param current_user:
@@ -426,12 +442,12 @@ def delete_rule(current_user, rule_id, model_name, route_model, rule_type):
:param route_model:
:return:
"""
- model = db.session.get(model_name, rule_id)
+ model: Optional[Union[Flowspec4, Flowspec6, RTBH]] = db.session.get(model_name, rule_id)
if model:
if check_access_rights(current_user, model.user_id):
# withdraw route
- command = route_model(model, WITHDRAW)
- route = Route(
+ command: str = route_model(model, WITHDRAW)
+ route: Route = Route(
author=f"{current_user['uuid']} / {current_user['org']}",
source=RouteSources.API,
command=command,
@@ -455,14 +471,13 @@ def delete_rule(current_user, rule_id, model_name, route_model, rule_type):
return jsonify({"message": "rule {} not found ".format(rule_id)}), 404
-def token_test_get(current_user):
+def token_test_get(current_user: Dict[str, Any]) -> Tuple[Response, int]:
"""
Return IPv4 rule
:param current_user:
- :param rule_id:
:return:
"""
- my_response = {
+ my_response: Dict[str, Any] = {
"message": "token works as expected",
"uuid": current_user["uuid"],
"readonly": current_user["readonly"],
@@ -470,7 +485,7 @@ def token_test_get(current_user):
return jsonify(my_response), 200
-def get_form_errors(form):
+def get_form_errors(form: Union[IPv4Form, IPv6Form, RTBHForm]) -> Union[Dict[str, Any], bool]:
# if the only error is in CSRF then it is ok - csrf is exempt for this view
try:
del form.errors["csrf_token"]
diff --git a/flowapp/views/dashboard.py b/flowapp/views/dashboard.py
index c709f11f..44319272 100644
--- a/flowapp/views/dashboard.py
+++ b/flowapp/views/dashboard.py
@@ -59,10 +59,8 @@ def index(rtype=None, rstate="active"):
# params sanitization
if rtype not in current_app.config["DASHBOARD"].keys():
- print("DEBUG rtype not in dashboard keys config")
return abort(404)
if rstate not in COMP_FUNCS.keys():
- print("DEBUG rstate not in dashboard keys config")
return abort(404)
if sum(session["user_role_ids"]) == 1:
rstate = "active"
diff --git a/flowapp/views/rules.py b/flowapp/views/rules.py
index e3b82a1a..7a50dffb 100644
--- a/flowapp/views/rules.py
+++ b/flowapp/views/rules.py
@@ -502,7 +502,6 @@ def ipv4_rule():
for error in errors:
current_app.logger.debug("Error in the %s field - %s" % (getattr(form, field).label.text, error))
- print("NOW", datetime.now())
default_expires = datetime.now() + timedelta(hours=1)
form.expires.data = default_expires
@@ -598,8 +597,6 @@ def rtbh_rule():
default_expires = datetime.now() + timedelta(days=7)
form.expires.data = default_expires
- print(whitelistable)
-
return render_template(
"forms/rtbh_rule.html", form=form, action_url=url_for("rules.rtbh_rule"), whitelistable=whitelistable
)
diff --git a/flowapp/views/whitelist.py b/flowapp/views/whitelist.py
index 39a9352a..9cc85e4a 100644
--- a/flowapp/views/whitelist.py
+++ b/flowapp/views/whitelist.py
@@ -40,7 +40,6 @@ def add():
for error in errors:
current_app.logger.debug("Error in the %s field - %s" % (getattr(form, field).label.text, error))
- print("NOW", datetime.now())
default_expires = datetime.now() + timedelta(hours=1)
form.expires.data = default_expires
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..844b05c2
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,66 @@
+[build-system]
+requires = ["setuptools>=61.0", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "exafs"
+dynamic = ["version"]
+authors = [
+ {name = "CESNET / Jiri Vrany, Petr Adamec, Josef Verich, Jakub Man"}
+]
+description = "Tool for creation, validation, and execution of ExaBGP messages."
+readme = "README.md"
+license = {text = "MIT"}
+requires-python = ">=3.9"
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+]
+dependencies = [
+ "Flask>=2.0.2",
+ "Flask-SQLAlchemy>=2.2",
+ "Flask-SSO>=0.4.0",
+ "Flask-WTF>=1.0.0",
+ "Flask-Migrate>=3.0.0",
+ "Flask-Script>=2.0.0",
+ "Flask-Session",
+ "PyJWT>=2.4.0",
+ "PyMySQL>=1.0.0",
+ "requests>=2.20.0",
+ "babel>=2.7.0",
+ "email_validator>=1.1",
+ "pika>=1.3.0",
+ "loguru",
+ "flasgger",
+ "python-dotenv",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=7.0.0",
+ # Add other dev-only dependencies here if needed
+ # "black>=22.0",
+ # "mypy>=0.950",
+ # "flake8>=4.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/CESNET/exafs"
+Repository = "https://github.com/CESNET/exafs"
+Issues = "https://github.com/CESNET/exafs/issues"
+
+[tool.setuptools.dynamic]
+version = {attr = "flowapp.__about__.__version__"}
+
+[tool.setuptools.packages.find]
+include = ["flowapp*"]
+
+[tool.setuptools.package-data]
+"*" = ["*"]
\ No newline at end of file
diff --git a/requirements-backup.txt b/requirements-backup.txt
deleted file mode 100644
index 33cf1c1e..00000000
--- a/requirements-backup.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-appdirs>=1.4.2
-asn1crypto>=0.24.0
-attrs>=17.4.0
-blinker>=1.4
-certifi>=2018.8.24
-cffi>=1.11.5
-chardet>=3.0.4
-click>=6.7
-cryptography>=2.3
-cryptography-vectors>=2.3.1
-enum34>=1.1.6
-Flask>=1.0.2
-Flask-SQLAlchemy>=2.2
-Flask-SSO>=0.4.0
-Flask-WTF>=0.14.2
-funcsigs>=1.0.2
-honcho>=0.7.1
-idna>=2.7
-ipaddress>=1.0.22
-itsdangerous>=0.24
-Jinja2>=2.10
-MarkupSafe>=0.23
-MySQL-python>=1.2.5
-packaging>=16.8
-pluggy>=0.6.0
-py>=1.5.2
-pycparser>=2.18
-PyJWT>=1.6.4
-PyMySQL>=0.7.10
-pyparsing>=2.1.10
-pytest>=3.4.0
-requests>=2.20.0
-six>=1.11.0
-SQLAlchemy>=1.1.6
-urllib3>=1.21.1
-uWSGI>=2.0.14
-Werkzeug>=0.14.1
-WTForms>=2.1
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index fb33ad36..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Flask>=2.0.2
-Flask-SQLAlchemy>=2.2
-Flask-SSO>=0.4.0
-Flask-WTF>=1.0.0
-Flask-Migrate>=3.0.0
-Flask-Script>=2.0.0
-Flask-Session
-PyJWT>=2.4.0
-PyMySQL>=1.0.0
-pytest>=7.0.0
-requests>=2.20.0
-babel>=2.7.0
-email_validator>=1.1
-pika>=1.3.0
-loguru
-flasgger
-python-dotenv
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100755
index c28a06d8..00000000
--- a/setup.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""
-Author(s):
-Jiri Vrany
-Petr Adamec
-Jakub Man
-
-Setuptools configuration
-"""
-
-import setuptools
-
-# Import the __version__ variable without having to import the flowapp package.
-# This prevents missing dependency error in new virtual environments.
-with open("flowapp/__about__.py") as f:
- exec(f.read())
-
-setuptools.setup(
- name="exafs",
- version=__version__, # noqa: F821
- author="CESNET / Jiri Vrany, Petr Adamec, Josef Verich, Jakub Man",
- description="Tool for creation, validation, and execution of ExaBGP messages.",
- url="https://github.com/CESNET/exafs",
- license="MIT",
- py_modules=[
- "flowapp",
- ],
- packages=setuptools.find_packages(),
- include_package_data=True,
- python_requires=">=3.11",
- install_requires=[
- "Flask>=2.0.2",
- "Flask-SQLAlchemy>=2.2",
- "Flask-SSO>=0.4.0",
- "Flask-WTF>=1.0.0",
- "Flask-Migrate>=3.0.0",
- "Flask-Script>=2.0.0",
- "PyJWT>=2.4.0",
- "PyMySQL>=1.0.0",
- "pytest>=7.0.0",
- "requests>=2.20.0",
- "babel>=2.7.0",
- "mysqlclient>=2.0.0",
- "email_validator>=1.1",
- "pika>=1.3.0",
- ],
-)