diff --git a/netsocadmin/login_tools.py b/netsocadmin/login_tools.py index 1287ade..051d730 100644 --- a/netsocadmin/login_tools.py +++ b/netsocadmin/login_tools.py @@ -54,8 +54,24 @@ def protected_page(view_func: typing.Callable[..., None]) -> typing.Callable[... @functools.wraps(view_func) def protected_view_func(*args, **kwargs): if config.LOGGED_IN_KEY not in flask.session or not flask.session[config.LOGGED_IN_KEY]: - return flask.redirect("?asdf=lol") - return flask.render_template("index.html", error_message="") + return flask.redirect("/?asdf=lol") + return view_func(*args, **kwargs) + return protected_view_func + + +def admin_page(view_func: typing.Callable[..., None]) -> typing.Callable[..., None]: + """ + admin_page is a route function decorator which will check that a user + is logged in and is an admin before allowing the decorated view function to be shown. If the + user is not logged in, it will redirect them to the index page. If the user + is logged in but not an admin, it will rediret them to the tools page. + """ + @functools.wraps(view_func) + def protected_view_func(*args, **kwargs): + if config.LOGGED_IN_KEY not in flask.session or not flask.session[config.LOGGED_IN_KEY]: + return flask.redirect("/?asdf=lol") + if "admin" not in flask.session or not flask.session["admin"]: + return flask.redirect("/tools") return view_func(*args, **kwargs) return protected_view_func @@ -67,6 +83,13 @@ def is_logged_in(): return config.LOGGED_IN_KEY in flask.session and flask.session[config.LOGGED_IN_KEY] +def is_admin(): + """ + Returns True if the user is currently logged in and is an admin. + """ + return is_logged_in() and "admin" in flask.session and flask.session["admin"] + + def is_correct_password(user: LoginUser) -> bool: """ is_correct_password tells you whether or not a given username + password diff --git a/netsocadmin/netsoc_admin.py b/netsocadmin/netsoc_admin.py index d82d962..3e9ab23 100644 --- a/netsocadmin/netsoc_admin.py +++ b/netsocadmin/netsoc_admin.py @@ -81,6 +81,7 @@ def internal_error(e): app.add_url_rule('/tools/mysql', view_func=routes.MySQLView.as_view('mysql')) app.add_url_rule('/tools/shells', view_func=routes.ShellsView.as_view('shells')) app.add_url_rule('/tools/backups', view_func=routes.BackupsView.as_view('backups')) +app.add_url_rule('/fail2ban', view_func=routes.Fail2BanView.as_view('fail2ban')) if __name__ == '__main__': diff --git a/netsocadmin/routes/__init__.py b/netsocadmin/routes/__init__.py index b898551..307dd02 100644 --- a/netsocadmin/routes/__init__.py +++ b/netsocadmin/routes/__init__.py @@ -8,6 +8,7 @@ from .tools.shells import ChangeShell, ShellsView from .tools.sudo import CompleteSudo, Sudo from .tools.wordpress import WordpressInstall, WordpressView +from .tools.fail2ban import Fail2BanView from .tutorials import Tutorials from .view import TemplateView @@ -41,6 +42,7 @@ "HelpView", "WordpressInstall", "WordpressView", + "Fail2BanView", # Tutorials "Tutorials", ] diff --git a/netsocadmin/routes/tools/fail2ban.py b/netsocadmin/routes/tools/fail2ban.py new file mode 100644 index 0000000..bc7dab6 --- /dev/null +++ b/netsocadmin/routes/tools/fail2ban.py @@ -0,0 +1,28 @@ +# stdlib +import logging + +# lib +import flask + +# local +import help_post + +from .index import AdminToolView + + +class Fail2BanView(AdminToolView): + """ + Route: fail2ban + """ + template_file = "fail2ban.html" + + page_title = "Fail2Ban" + + active = "fail2ban" + # Logger instance + logger = logging.getLogger("netsocadmin.fail2ban") + + def dispatch_request(self) -> str: + server = flask.request.args.get("server", "leela") + + return self.render(server=server) diff --git a/netsocadmin/routes/tools/index.py b/netsocadmin/routes/tools/index.py index e6e7c42..619d626 100644 --- a/netsocadmin/routes/tools/index.py +++ b/netsocadmin/routes/tools/index.py @@ -34,6 +34,28 @@ class ProtectedToolView(TemplateView): methods = ["GET"] +class AdminView(View): + """ + Super class for all of the admin routes that dont render a template + """ + # Decorate all subclasses with the following decorators + decorators = [login_tools.admin_page] + # Specify the default method(s) that are allowed to be used to access the route + # This can be overriden on a per view basis + methods = ["GET"] + + +class AdminToolView(TemplateView): + """ + Super class for all of the admin routes that render the tools template + """ + # Decorate all subclasses with the following decorators + decorators = [login_tools.admin_page] + # Specify the default method(s) that are allowed to be used to access the route + # This can be overriden on a per view basis + methods = ["GET"] + + class ToolIndex(ProtectedToolView): """ Route: tools diff --git a/netsocadmin/routes/tools/shells.py b/netsocadmin/routes/tools/shells.py index 81bba1d..eba2925 100644 --- a/netsocadmin/routes/tools/shells.py +++ b/netsocadmin/routes/tools/shells.py @@ -5,6 +5,8 @@ import config +import login_tools + from .index import ProtectedToolView, ProtectedView diff --git a/netsocadmin/routes/view.py b/netsocadmin/routes/view.py index 5d1b904..5794ff0 100644 --- a/netsocadmin/routes/view.py +++ b/netsocadmin/routes/view.py @@ -28,6 +28,7 @@ def render(self, **data: Union[str, bool]) -> str: return flask.render_template( self.template_file, is_logged_in=login_tools.is_logged_in(), + is_admin=login_tools.is_admin(), username=flask.session["username"] if "username" in flask.session else None, page_title=self.page_title, active=self.active, diff --git a/netsocadmin/templates/fail2ban.html b/netsocadmin/templates/fail2ban.html new file mode 100644 index 0000000..fbeb78b --- /dev/null +++ b/netsocadmin/templates/fail2ban.html @@ -0,0 +1,24 @@ +{% extends "page-skeleton.html" %} +{% block head %} +{{ super() }} +{% endblock %} + +{% block body %} +{{ super() }} +
+
+ {% if help_error %} +

{{ help_error }}

+ {% endif %} +
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/netsocadmin/templates/page-skeleton.html b/netsocadmin/templates/page-skeleton.html index e8b42ff..2d683ee 100644 --- a/netsocadmin/templates/page-skeleton.html +++ b/netsocadmin/templates/page-skeleton.html @@ -68,7 +68,7 @@ background-color: #eee; display: flex; min-height: 100vh; - flex-direction: column; + flex-direction: column; } .error-msg { @@ -76,9 +76,12 @@ } footer { - {% if is_logged_in %} + {% if is_logged_in %} + margin-left: 240px; - {% endif %} + + {% endif %} + background-color: rgba(0, 0, 0, 0.0) !important; } @@ -112,18 +115,23 @@ } main { - {% if is_logged_in %} + {% if is_logged_in %} + margin-right: 40px; margin-left: 280px; - {% else %} + + {% else %} + margin: 0 auto; - {% endif %} + + {% endif %} + max-width: 90%; flex: 1 0 auto; } .nav-wrapper { - padding-right: 5%; + padding-right: 5%; padding-left: 260px } @@ -134,15 +142,17 @@ font-weight: 600 !important; /* color: rgb(105, 105, 105) !important; */ } - - .fa, .far, .fas { - font-family: "Font Awesome 5 Free", "Roboto", sans-serif; - } - - .fab { - font-family: "Font Awesome 5 Brands", "Roboto", sans-serif; - } - + + .fa, + .far, + .fas { + font-family: "Font Awesome 5 Free", "Roboto", sans-serif; + } + + .fab { + font-family: "Font Awesome 5 Brands", "Roboto", sans-serif; + } + @media only screen and (min-width: 992px) { .row .col:first-child { padding: 0 0.75rem 0 0; @@ -159,7 +169,7 @@ min-width: 95%; } - main > div { + main>div { width: 100%; } @@ -168,7 +178,7 @@ } .nav-wrapper { - padding-right: 0px; + padding-right: 0px; padding-left: 1em; } @@ -191,7 +201,11 @@ line-height: 40px; } - .fa, .fab, .fal, .far, .fas { + .fa, + .fab, + .fal, + .far, + .fas { line-height: 3; } } @@ -223,7 +237,8 @@ - - {% if is_logged_in %} - - {% endif %} + +
  • + + + MySQL + + +
  • +
  • + + + Backups + + +
  • +
  • + + + WordPress + + +
  • +
  • + + + Login Shell + + +
  • +
  • + + + Get Sudo + + +
  • +
  • + + + Help + + +
  • + {% if is_admin == True %} +
  • + + + Fail2Ban + + +
  • + {% endif %} + + {% endif %}
    @@ -438,4 +483,4 @@

    Some dope ass page content

    {% endblock %} -#} +#} \ No newline at end of file