@@ -23,6 +23,7 @@ Guide | |||||
mkdir logs | mkdir logs | ||||
sudo chmod 0600 config/config.yml data/db.sqlite3 | sudo chmod 0600 config/config.yml data/db.sqlite3 | ||||
sudo chown www-data:www-data config/config.yml data data/db.sqlite3 logs | sudo chown www-data:www-data config/config.yml data data/db.sqlite3 logs | ||||
... # TODO: convert these into scripts, add module instructions | |||||
### Test | ### Test | ||||
@@ -35,6 +35,7 @@ config.install(app) | |||||
@app.before_request | @app.before_request | ||||
def prepare_request(): | def prepare_request(): | ||||
"""Set up the Flask global context variable with important objects.""" | |||||
g.auth = auth | g.auth = auth | ||||
g.config = config | g.config = config | ||||
g.eve = eve | g.eve = eve | ||||
@@ -47,6 +48,11 @@ app.teardown_appcontext(Database.post_hook) | |||||
@app.route("/") | @app.route("/") | ||||
@app.catch_exceptions | @app.catch_exceptions | ||||
def index(): | def index(): | ||||
"""Render and return the index page. | |||||
This is a informational landing page for non-logged-in users, and the corp | |||||
homepage for those who are logged in. | |||||
""" | |||||
success, _ = try_func(auth.is_authenticated) | success, _ = try_func(auth.is_authenticated) | ||||
if success: | if success: | ||||
module = config.get("modules.home") | module = config.get("modules.home") | ||||
@@ -58,6 +64,7 @@ def index(): | |||||
@app.route("/login", methods=["GET", "POST"]) | @app.route("/login", methods=["GET", "POST"]) | ||||
@app.catch_exceptions | @app.catch_exceptions | ||||
def login(): | def login(): | ||||
"""Handle the last step of a SSO login request.""" | |||||
code = request.args.get("code") | code = request.args.get("code") | ||||
state = request.args.get("state") | state = request.args.get("state") | ||||
@@ -71,6 +78,7 @@ def login(): | |||||
@app.route("/logout", methods=["GET", "POST"]) | @app.route("/logout", methods=["GET", "POST"]) | ||||
@app.catch_exceptions | @app.catch_exceptions | ||||
def logout(): | def logout(): | ||||
"""Log the user out (POST), or ask them to confirm a log out (GET).""" | |||||
if request.method == "GET": | if request.method == "GET": | ||||
return render_template("logout.mako") | return render_template("logout.mako") | ||||
@@ -82,12 +90,14 @@ def logout(): | |||||
@app.catch_exceptions | @app.catch_exceptions | ||||
@app.route_restricted | @app.route_restricted | ||||
def set_style(style): | def set_style(style): | ||||
"""Set the user's style preference.""" | |||||
if not auth.set_character_style(style): | if not auth.set_character_style(style): | ||||
abort(404) | abort(404) | ||||
return "", 204 | return "", 204 | ||||
@app.errorhandler(404) | @app.errorhandler(404) | ||||
def page_not_found(err): | def page_not_found(err): | ||||
"""Render and return the 404 error template.""" | |||||
return render_template("404.mako"), 404 | return render_template("404.mako"), 404 | ||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
@@ -10,8 +10,6 @@ from .exceptions import AccessDeniedError | |||||
__all__ = ["AuthManager"] | __all__ = ["AuthManager"] | ||||
_SCOPES = [] # ... | |||||
class AuthManager: | class AuthManager: | ||||
"""Authentication manager. Handles user access and management.""" | """Authentication manager. Handles user access and management.""" | ||||
EXPIRY_THRESHOLD = 30 | EXPIRY_THRESHOLD = 30 | ||||
@@ -316,7 +314,7 @@ class AuthManager: | |||||
"""Return a complete EVE SSO link that the user can use to log in.""" | """Return a complete EVE SSO link that the user can use to log in.""" | ||||
cid = self._config.get("auth.client_id") | cid = self._config.get("auth.client_id") | ||||
target = url_for("login", _external=True, _scheme=self._config.scheme) | target = url_for("login", _external=True, _scheme=self._config.scheme) | ||||
scopes = _SCOPES | |||||
scopes = self._config.collect_scopes() | |||||
state = self._get_state_hash() | state = self._get_state_hash() | ||||
return self._eve.sso.get_authorize_url(cid, target, scopes, state) | return self._eve.sso.get_authorize_url(cid, target, scopes, state) | ||||
@@ -89,3 +89,12 @@ class Config: | |||||
return yaml.load(fp) | return yaml.load(fp) | ||||
except FileNotFoundError: | except FileNotFoundError: | ||||
return None | return None | ||||
def collect_scopes(self): | |||||
"""Return a list of SSO scopes required for all/regular users.""" | |||||
scopes = set() | |||||
for module in self.modules: | |||||
scopes |= module.scopes() | |||||
if not scopes: | |||||
scopes.add("publicData") | |||||
return sorted(list(scopes)) |
@@ -51,3 +51,9 @@ class Module: | |||||
"""Return a navigation bar HTML snippet for this module, or None.""" | """Return a navigation bar HTML snippet for this module, or None.""" | ||||
if hasattr(self._module, "navitem"): | if hasattr(self._module, "navitem"): | ||||
return self._module.navitem() | return self._module.navitem() | ||||
def scopes(self): | |||||
"""Return a set of SSO scopes required by this module.""" | |||||
if hasattr(self._module, "SCOPES"): | |||||
return set(self._module.SCOPES) | |||||
return set() |
@@ -15,9 +15,11 @@ def get_current(): | |||||
return setting | return setting | ||||
def home(): | def home(): | ||||
"""Render and return the main campaign page.""" | |||||
return render_template("campaigns/campaign.mako", current=get_current()) | return render_template("campaigns/campaign.mako", current=get_current()) | ||||
def navitem(): | def navitem(): | ||||
"""Render and return the navigation item for this module.""" | |||||
current = get_current() | current = get_current() | ||||
if current: | if current: | ||||
result = render_template("campaigns/navitem.mako", current=current) | result = render_template("campaigns/navitem.mako", current=current) | ||||
@@ -25,10 +27,12 @@ def navitem(): | |||||
@blueprint.rroute("/campaign") | @blueprint.rroute("/campaign") | ||||
def campaign(): | def campaign(): | ||||
"""Render and return the current campaign page.""" | |||||
return home() | return home() | ||||
@blueprint.rroute("/settings/campaign/<campaign>", methods=["POST"]) | @blueprint.rroute("/settings/campaign/<campaign>", methods=["POST"]) | ||||
def set_campaign(campaign): | def set_campaign(campaign): | ||||
"""Update the user's currently selected campaign.""" | |||||
if campaign not in config["enabled"]: | if campaign not in config["enabled"]: | ||||
abort(404) | abort(404) | ||||
g.auth.set_character_modprop("campaigns", "current", campaign) | g.auth.set_character_modprop("campaigns", "current", campaign) | ||||
@@ -2,5 +2,7 @@ | |||||
# ... | # ... | ||||
SCOPES = {"esi-corporations.read_corporation_membership.v1"} | |||||
def navitem(): | def navitem(): | ||||
return "Members" | return "Members" |
@@ -38,7 +38,8 @@ auth: | |||||
# https://developers.eveonline.com/applications for this corp's website. | # https://developers.eveonline.com/applications for this corp's website. | ||||
# Set the callback URL to http(s)://<your domain>/login (match the protocol | # Set the callback URL to http(s)://<your domain>/login (match the protocol | ||||
# with "site.https" above) and the scopes to whatever is required by the | # with "site.https" above) and the scopes to whatever is required by the | ||||
# modules you've enabled. | |||||
# modules you've enabled. If none of your modules require scopes, select at | |||||
# least "publicData". | |||||
# SSO client ID: | # SSO client ID: | ||||
client_id: a290afea820b8dd8c46d3883898ab66d | client_id: a290afea820b8dd8c46d3883898ab66d | ||||
# SSO client secret: | # SSO client secret: | ||||