Commit 4360efbc authored by Pietro Albini's avatar Pietro Albini

Add permissions support

parent f94329f7
Pipeline #76 passed with stage
in 0 seconds
...@@ -51,6 +51,7 @@ def run(data, gunicorn_config, port, public, workers, debug): ...@@ -51,6 +51,7 @@ def run(data, gunicorn_config, port, public, workers, debug):
if debug: if debug:
extra_files = [ extra_files = [
os.path.join(src_directory, "data/navbar.yml"), os.path.join(src_directory, "data/navbar.yml"),
os.path.join(src_directory, "data/permissions.yml"),
] ]
app.run(debug=True, port=port, host=host, extra_files=extra_files) app.run(debug=True, port=port, host=host, extra_files=extra_files)
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import functools
import uuid import uuid
import flask import flask
...@@ -106,8 +107,56 @@ class Permissions: ...@@ -106,8 +107,56 @@ class Permissions:
self.config = yaml.load(raw.decode("utf-8")) self.config = yaml.load(raw.decode("utf-8"))
def allowed_teams(self): def allowed_teams(self):
"""Return a list of teams allowed to log in"""
return list(self.config["teams"].keys()) return list(self.config["teams"].keys())
def check(self, name):
"""Check if the current user has the permission"""
# The user must be authenticated
if "auth_teams" not in flask.g:
return False
# The permission must exist
if name not in self.config["permissions"]:
raise RuntimeError("Missing permission: %s" % name)
# Check if one of the user's teams has the permission
for team in flask.g.auth_teams:
if self.config["teams"][team] == "*":
return True
elif name in self.config["teams"][team]:
return True
return False
def permission(perms):
"""Process the endpoint only if the user has permission"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Don't check anything if the user is not logged in
if "auth_id" not in flask.g:
return flask.abort(403)
# Generate the list of allowed permissions
names = []
if type(perms) == dict:
for name, condition in perms.items():
if condition(*args, **kwargs):
names.append(name)
else:
names.append(perms)
# Check each allowed permission
for name in names:
if flask.current_app.permissions.check(name):
return func(*args, **kwargs)
return flask.abort(403)
return wrapper
return decorator
def prepare_blueprint(app): def prepare_blueprint(app):
"""Prepare the auth blueprint""" """Prepare the auth blueprint"""
...@@ -119,7 +168,13 @@ def prepare_blueprint(app): ...@@ -119,7 +168,13 @@ def prepare_blueprint(app):
extension_responses=[openid_teams.TeamsResponse], extension_responses=[openid_teams.TeamsResponse],
) )
sessions = Sessions(app.db) sessions = Sessions(app.db)
permissions = Permissions() app.permissions = Permissions()
@app.context_processor
def add_permission_function():
return {
"permission": app.permissions.check,
}
@app.before_request @app.before_request
def check_auth(): def check_auth():
...@@ -156,7 +211,7 @@ def prepare_blueprint(app): ...@@ -156,7 +211,7 @@ def prepare_blueprint(app):
"https://login.ubuntu.com/+openid", "https://login.ubuntu.com/+openid",
ask_for=["nickname"], ask_for=["nickname"],
extensions=[ extensions=[
openid_teams.TeamsRequest(permissions.allowed_teams()) openid_teams.TeamsRequest(app.permissions.allowed_teams())
], ],
) )
else: else:
...@@ -164,6 +219,7 @@ def prepare_blueprint(app): ...@@ -164,6 +219,7 @@ def prepare_blueprint(app):
return flask.redirect(flask.url_for("pages.index")) return flask.redirect(flask.url_for("pages.index"))
@bp.route("/logout") @bp.route("/logout")
@permission("auth.logout")
def logout(): def logout():
sessions.delete(flask.session["auth"]) sessions.delete(flask.session["auth"])
del flask.session["auth"] del flask.session["auth"]
...@@ -173,6 +229,7 @@ def prepare_blueprint(app): ...@@ -173,6 +229,7 @@ def prepare_blueprint(app):
@bp.route("/sessions") @bp.route("/sessions")
@permission("auth.sessions.manage")
def sessions_list(): def sessions_list():
return flask.render_template( return flask.render_template(
"auth/sessions.html", "auth/sessions.html",
...@@ -180,11 +237,16 @@ def prepare_blueprint(app): ...@@ -180,11 +237,16 @@ def prepare_blueprint(app):
) )
@bp.route("/sessions/+all/revoke") @bp.route("/sessions/+all/revoke")
@permission("auth.sessions.manage")
def sessions_revoke_all(): def sessions_revoke_all():
sessions.delete_all(flask.g.auth_id) sessions.delete_all(flask.g.auth_id)
return flask.redirect(flask.url_for(".sessions_list")) return flask.redirect(flask.url_for(".sessions_list"))
@bp.route("/sessions/<id>") @bp.route("/sessions/<id>")
@permission({
"auth.sessions.manage": lambda id: True,
"auth.sessions.own": lambda id: flask.g.auth_id == id,
})
def sessions_show(id): def sessions_show(id):
data = sessions.get(id) data = sessions.get(id)
if data is None: if data is None:
...@@ -204,6 +266,10 @@ def prepare_blueprint(app): ...@@ -204,6 +266,10 @@ def prepare_blueprint(app):
) )
@bp.route("/sessions/<id>/revoke") @bp.route("/sessions/<id>/revoke")
@permission({
"auth.sessions.manage": lambda id: True,
"auth.sessions.own": lambda id: flask.g.auth_id == id,
})
def sessions_revoke(id): def sessions_revoke(id):
sessions.delete(id) sessions.delete(id)
return flask.redirect(flask.url_for(".sessions_list")) return flask.redirect(flask.url_for(".sessions_list"))
......
permissions: [] permissions:
- auth.logout
- auth.sessions.manage
- auth.sessions.own
teams: teams:
ubuntu-it-www: "*"
ubuntu-it-council: "*" ubuntu-it-council: "*"
ubuntu-it-members: [] ubuntu-it-www: "*"
ubuntu-it-newsletter: [] ubuntu-it-members:
- auth.sessions.own
- auth.logout
ubuntu-it-newsletter:
- auth.sessions.own
- auth.logout
...@@ -48,15 +48,21 @@ ...@@ -48,15 +48,21 @@
<nav class="sites-list"> <nav class="sites-list">
<div class="container"> <div class="container">
<ul class="left"> <ul class="left">
{% if permission("auth.sessions.manage") %}
<li><a href="{{ url_for("auth.sessions_list") }}"> <li><a href="{{ url_for("auth.sessions_list") }}">
Sessioni attive: {{ g.auth_sessions_count }} Sessioni attive: {{ g.auth_sessions_count }}
</a></li> </a></li>
{% endif %}
</ul> </ul>
<ul class="right"> <ul class="right">
{% if permission("auth.sessions.own") %}
<li><a href="{{ url_for("auth.sessions_show", id=g.auth_id) }}"> <li><a href="{{ url_for("auth.sessions_show", id=g.auth_id) }}">
{{ g.auth_name }} {{ g.auth_name }}
</a></li> </a></li>
{% endif %}
{% if permission("auth.logout") %}
<li><a href="{{ url_for("auth.logout") }}">Esci</a></li> <li><a href="{{ url_for("auth.logout") }}">Esci</a></li>
{% endif %}
</ul> </ul>
</div> </div>
</nav> </nav>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment