authentication by reverse proxy
This commit is contained in:
parent
c6016c6116
commit
328f41b85c
27
README.md
27
README.md
|
@ -336,6 +336,33 @@ Put them in your `realms-wiki.json` config file. Use the example below.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### Authentication by reverse proxy
|
||||||
|
|
||||||
|
If you configured realms behind a reverse-proxy or a single-sign-on, it is possible to delegate authentication to
|
||||||
|
the proxy.
|
||||||
|
|
||||||
|
"AUTH_PROXY": true
|
||||||
|
|
||||||
|
Note: of course with that setup you must ensure that **Realms is only accessible through the proxy**.
|
||||||
|
|
||||||
|
Example Nginx configuration:
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header REMOTE_USER $remote_user;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:5000/;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
|
||||||
|
By default, Realms will look for the user ID in `REMOTE_USER` HTTP header. You can specify another header name with:
|
||||||
|
|
||||||
|
"AUTH_PROXY_HEADER_NAME": "LOGGED_IN_USER"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
realms-wiki start
|
realms-wiki start
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
# Set default encoding to UTF-8
|
# Set default encoding to UTF-8
|
||||||
reload(sys)
|
reload(sys)
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
|
@ -17,7 +18,7 @@ from functools import update_wrapper
|
||||||
import click
|
import click
|
||||||
from flask import Flask, request, render_template, url_for, redirect, g
|
from flask import Flask, request, render_template, url_for, redirect, g
|
||||||
from flask_cache import Cache
|
from flask_cache import Cache
|
||||||
from flask_login import LoginManager, current_user
|
from flask_login import LoginManager, current_user, logout_user
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_assets import Environment, Bundle
|
from flask_assets import Environment, Bundle
|
||||||
from flask_ldap_login import LDAPLoginManager
|
from flask_ldap_login import LDAPLoginManager
|
||||||
|
@ -215,6 +216,23 @@ def create_app(config=None):
|
||||||
if app.config.get('DB_URI'):
|
if app.config.get('DB_URI'):
|
||||||
db.metadata.create_all(db.get_engine(app))
|
db.metadata.create_all(db.get_engine(app))
|
||||||
|
|
||||||
|
if app.config["AUTH_PROXY"]:
|
||||||
|
logger = logging.getLogger("realms.auth")
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def proxy_auth():
|
||||||
|
from realms.modules.auth.proxy.models import User as ProxyUser
|
||||||
|
remote_user = request.environ.get(app.config["AUTH_PROXY_HEADER_NAME"])
|
||||||
|
if remote_user:
|
||||||
|
if current_user.is_authenticated():
|
||||||
|
if current_user.id == remote_user:
|
||||||
|
return
|
||||||
|
logger.info("login in realms and login by proxy are different: '{}'/'{}'".format(
|
||||||
|
current_user.id, remote_user))
|
||||||
|
logout_user()
|
||||||
|
logger.info("User logged in by proxy as '{}'".format(remote_user))
|
||||||
|
ProxyUser.do_login(remote_user)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
# Init plugins here if possible
|
# Init plugins here if possible
|
||||||
|
|
|
@ -100,6 +100,10 @@ class Config(object):
|
||||||
# Name of page that will act as home
|
# Name of page that will act as home
|
||||||
WIKI_HOME = 'home'
|
WIKI_HOME = 'home'
|
||||||
|
|
||||||
|
# Should we trust authentication set by a proxy
|
||||||
|
AUTH_PROXY = False
|
||||||
|
AUTH_PROXY_HEADER_NAME = "REMOTE_USER"
|
||||||
|
|
||||||
AUTH_LOCAL_ENABLE = True
|
AUTH_LOCAL_ENABLE = True
|
||||||
ALLOW_ANON = True
|
ALLOW_ANON = True
|
||||||
REGISTRATION_ENABLED = True
|
REGISTRATION_ENABLED = True
|
||||||
|
|
|
@ -5,8 +5,10 @@ from flask_login import login_url
|
||||||
|
|
||||||
from realms import login_manager
|
from realms import login_manager
|
||||||
|
|
||||||
|
|
||||||
modules = set()
|
modules = set()
|
||||||
|
|
||||||
|
|
||||||
@login_manager.unauthorized_handler
|
@login_manager.unauthorized_handler
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
|
|
@ -17,6 +17,7 @@ from . import modules
|
||||||
def load_user(auth_id):
|
def load_user(auth_id):
|
||||||
return Auth.load_user(auth_id)
|
return Auth.load_user(auth_id)
|
||||||
|
|
||||||
|
|
||||||
auth_users = {}
|
auth_users = {}
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +41,9 @@ class Auth(object):
|
||||||
def login_forms():
|
def login_forms():
|
||||||
forms = []
|
forms = []
|
||||||
for t in modules:
|
for t in modules:
|
||||||
forms.append(Auth.get_auth_user(t).login_form())
|
form = Auth.get_auth_user(t).login_form()
|
||||||
|
if form:
|
||||||
|
forms.append(form)
|
||||||
return "<hr />".join(forms)
|
return "<hr />".join(forms)
|
||||||
|
|
||||||
|
|
||||||
|
|
5
realms/modules/auth/proxy/__init__.py
Normal file
5
realms/modules/auth/proxy/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from realms.modules.auth.models import Auth
|
||||||
|
|
||||||
|
Auth.register('proxy')
|
42
realms/modules/auth/proxy/models.py
Normal file
42
realms/modules/auth/proxy/models.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from flask_login import login_user
|
||||||
|
|
||||||
|
from realms.modules.auth.models import BaseUser
|
||||||
|
|
||||||
|
|
||||||
|
users = {}
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseUser):
|
||||||
|
type = 'proxy'
|
||||||
|
|
||||||
|
def __init__(self, username, email='null@localhost.local', password="dummypassword"):
|
||||||
|
self.id = username
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auth_token_id(self):
|
||||||
|
return self.password
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load_user(*args, **kwargs):
|
||||||
|
return User.get_by_id(args[0])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_by_id(user_id):
|
||||||
|
return users.get(user_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def login_form():
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def do_login(user_id):
|
||||||
|
user = User(user_id)
|
||||||
|
users[user_id] = user
|
||||||
|
login_user(user, remember=True)
|
||||||
|
return True
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from flask import current_app, render_template, request, redirect, Blueprint, flash, url_for, session
|
from flask import current_app, render_template, request, redirect, Blueprint, flash, url_for, session
|
||||||
from flask_login import logout_user
|
from flask_login import logout_user, current_user
|
||||||
|
|
||||||
from .models import Auth
|
from .models import Auth
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ blueprint = Blueprint('auth', __name__, template_folder='templates')
|
||||||
@blueprint.route("/login", methods=['GET', 'POST'])
|
@blueprint.route("/login", methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
next_url = request.args.get('next') or url_for(current_app.config['ROOT_ENDPOINT'])
|
next_url = request.args.get('next') or url_for(current_app.config['ROOT_ENDPOINT'])
|
||||||
|
if current_user.is_authenticated():
|
||||||
|
return redirect(next_url)
|
||||||
session['next_url'] = next_url
|
session['next_url'] = next_url
|
||||||
return render_template("auth/login.html", forms=Auth.login_forms())
|
return render_template("auth/login.html", forms=Auth.login_forms())
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ blueprint = Blueprint('wiki', __name__, template_folder='templates',
|
||||||
|
|
||||||
@blueprint.route("/_commit/<sha>/<path:name>")
|
@blueprint.route("/_commit/<sha>/<path:name>")
|
||||||
def commit(name, sha):
|
def commit(name, sha):
|
||||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||||
return current_app.login_manager.unauthorized()
|
return current_app.login_manager.unauthorized()
|
||||||
|
|
||||||
cname = to_canonical(name)
|
cname = to_canonical(name)
|
||||||
|
@ -35,7 +35,7 @@ def commit(name, sha):
|
||||||
|
|
||||||
@blueprint.route(r"/_compare/<path:name>/<regex('\w+'):fsha><regex('\.{2,3}'):dots><regex('\w+'):lsha>")
|
@blueprint.route(r"/_compare/<path:name>/<regex('\w+'):fsha><regex('\.{2,3}'):dots><regex('\w+'):lsha>")
|
||||||
def compare(name, fsha, dots, lsha):
|
def compare(name, fsha, dots, lsha):
|
||||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||||
return current_app.login_manager.unauthorized()
|
return current_app.login_manager.unauthorized()
|
||||||
|
|
||||||
diff = g.current_wiki.get_page(name, sha=lsha).compare(fsha)
|
diff = g.current_wiki.get_page(name, sha=lsha).compare(fsha)
|
||||||
|
@ -50,7 +50,7 @@ def revert():
|
||||||
commit = request.form.get('commit')
|
commit = request.form.get('commit')
|
||||||
message = request.form.get('message', "Reverting %s" % cname)
|
message = request.form.get('message', "Reverting %s" % cname)
|
||||||
|
|
||||||
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous:
|
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous():
|
||||||
return dict(error=True, message="Anonymous posting not allowed"), 403
|
return dict(error=True, message="Anonymous posting not allowed"), 403
|
||||||
|
|
||||||
if cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
if cname in current_app.config.get('WIKI_LOCKED_PAGES'):
|
||||||
|
@ -72,7 +72,7 @@ def revert():
|
||||||
|
|
||||||
@blueprint.route("/_history/<path:name>")
|
@blueprint.route("/_history/<path:name>")
|
||||||
def history(name):
|
def history(name):
|
||||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||||
return current_app.login_manager.unauthorized()
|
return current_app.login_manager.unauthorized()
|
||||||
return render_template('wiki/history.html', name=name)
|
return render_template('wiki/history.html', name=name)
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ def _tree_index(items, path=""):
|
||||||
@blueprint.route("/_index", defaults={"path": ""})
|
@blueprint.route("/_index", defaults={"path": ""})
|
||||||
@blueprint.route("/_index/<path:path>")
|
@blueprint.route("/_index/<path:path>")
|
||||||
def index(path):
|
def index(path):
|
||||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||||
return current_app.login_manager.unauthorized()
|
return current_app.login_manager.unauthorized()
|
||||||
|
|
||||||
items = g.current_wiki.get_index()
|
items = g.current_wiki.get_index()
|
||||||
|
@ -218,7 +218,7 @@ def page_write(name):
|
||||||
if not cname:
|
if not cname:
|
||||||
return dict(error=True, message="Invalid name")
|
return dict(error=True, message="Invalid name")
|
||||||
|
|
||||||
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous:
|
if not current_app.config.get('ALLOW_ANON') and current_user.is_anonymous():
|
||||||
return dict(error=True, message="Anonymous posting not allowed"), 403
|
return dict(error=True, message="Anonymous posting not allowed"), 403
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
@ -261,7 +261,7 @@ def page_write(name):
|
||||||
@blueprint.route("/", defaults={'name': 'home'})
|
@blueprint.route("/", defaults={'name': 'home'})
|
||||||
@blueprint.route("/<path:name>")
|
@blueprint.route("/<path:name>")
|
||||||
def page(name):
|
def page(name):
|
||||||
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous:
|
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
|
||||||
return current_app.login_manager.unauthorized()
|
return current_app.login_manager.unauthorized()
|
||||||
|
|
||||||
cname = to_canonical(name)
|
cname = to_canonical(name)
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated() %}
|
||||||
<li class="dropdown user-avatar">
|
<li class="dropdown user-avatar">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||||
<span>
|
<span>
|
||||||
|
@ -68,7 +68,11 @@
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<!--<li><a href="{{ url_for('auth.settings') }}"><i class="fa fa-gear"></i> Settings</a></li>-->
|
<!--<li><a href="{{ url_for('auth.settings') }}"><i class="fa fa-gear"></i> Settings</a></li>-->
|
||||||
<li><a href="{{ url_for('auth.logout') }}"><i class="fa fa-power-off"></i> Logout</a></li>
|
{% if current_user.type != "proxy" %}
|
||||||
|
<li><a href="{{ url_for('auth.logout') }}"><i class="fa fa-power-off"></i> Logout</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><button class="btn btn-block" disabled="disabled"><i class="fa fa-power-off"></i> Logout</button></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -109,7 +113,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
var User = {};
|
var User = {};
|
||||||
User.is_authenticated = {{ current_user.is_authenticated|tojson }};
|
User.is_authenticated = {{ current_user.is_authenticated()|tojson }};
|
||||||
{% for attr in ['username', 'email'] %}
|
{% for attr in ['username', 'email'] %}
|
||||||
User.{{ attr }} = {{ current_user[attr]|tojson }};
|
User.{{ attr }} = {{ current_user[attr]|tojson }};
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
Loading…
Reference in a new issue