Skip to content

Commit

Permalink
allow creating users from reverse proxy headers
Browse files Browse the repository at this point in the history
allowing login via reverse proxy auth is convenient, but it's not
convenient to have to create the users in advance. this PR allows users
to be optionally created if they don't already exist. we provide this as
an option in the UI
  • Loading branch information
igor47 committed Jun 7, 2024
1 parent 6a14e2c commit a326949
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 1 deletion.
2 changes: 2 additions & 0 deletions cps/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,8 @@ def _configuration_update_helper():
# Reverse proxy login configuration
_config_checkbox(to_save, "config_allow_reverse_proxy_header_login")
_config_string(to_save, "config_reverse_proxy_login_header_name")
_config_checkbox(to_save, "config_reverse_proxy_create_users")
_config_string(to_save, "config_reverse_proxy_email_header_name")

# OAuth configuration
if config.config_login_type == constants.LOGIN_OAUTH:
Expand Down
4 changes: 3 additions & 1 deletion cps/config_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class _Settings(_Base):
config_random_books = Column(Integer, default=4)
config_authors_max = Column(Integer, default=0)
config_read_column = Column(Integer, default=0)
config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+')
config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+')
config_theme = Column(Integer, default=0)

config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
Expand Down Expand Up @@ -147,6 +147,8 @@ class _Settings(_Base):

config_reverse_proxy_login_header_name = Column(String)
config_allow_reverse_proxy_header_login = Column(Boolean, default=False)
config_reverse_proxy_create_users = Column(Boolean, default=False)
config_reverse_proxy_email_header_name = Column(String)

schedule_start_time = Column(Integer, default=4)
schedule_duration = Column(Integer, default=10)
Expand Down
8 changes: 8 additions & 0 deletions cps/templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ <h2>{{_('Configuration')}}</h2>
<div class="col-xs-6 col-sm-7">{{_('Reverse Proxy Header Name')}}</div>
<div class="col-xs-6 col-sm-5">{{ config.config_reverse_proxy_login_header_name }}</div>
</div>
<div class="row">
<div class="col-xs-6 col-sm-7">{{_('Create Reverse Proxy Users')}}</div>
<div class="col-xs-6 col-sm-5">{{ display_bool_setting(config.config_reverse_proxy_create_users) }}</div>
</div>
<div class="row">
<div class="col-xs-6 col-sm-7">{{_('Reverse Proxy Email Header Name')}}</div>
<div class="col-xs-6 col-sm-5">{{ config.config_reverse_proxy_email_header_name }}</div>
</div>
{% endif %}
</div>
<a class="btn btn-default" id="db_config" href="{{url_for('admin.db_configuration')}}">{{_('Edit Calibre Database Configuration')}}</a>
Expand Down
8 changes: 8 additions & 0 deletions cps/templates/config_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ <h4 class="panel-title">
<label for="config_reverse_proxy_login_header_name">{{_('Reverse Proxy Header Name')}}</label>
<input type="text" class="form-control" id="config_reverse_proxy_login_header_name" name="config_reverse_proxy_login_header_name" value="{% if config.config_reverse_proxy_login_header_name != None %}{{ config.config_reverse_proxy_login_header_name }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<input type="checkbox" id="config_reverse_proxy_create_users" name="config_reverse_proxy_create_users" {% if config.config_reverse_proxy_create_users %}checked{% endif %}>
<label for="config_reverse_proxy_create_users">{{_('Create Reverse Proxy Users')}}</label>
</div>
<div class="form-group">
<label for="config_reverse_proxy_email_header_name">{{_('Reverse Proxy Email Header Name')}}</label>
<input type="text" class="form-control" id="config_reverse_proxy_email_header_name" name="config_reverse_proxy_email_header_name" value="{% if config.config_reverse_proxy_email_header_name != None %}{{ config.config_reverse_proxy_email_header_name }}{% endif %}" autocomplete="off">
</div>
</div>
{% if not config.config_is_initial %}
{% if feature_support['ldap'] or feature_support['oauth'] %}
Expand Down
42 changes: 42 additions & 0 deletions cps/usermanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

from . import lm, ub, config, constants, services, logger, limiter

from .helper import generate_random_password, generate_password_hash, check_email

log = logger.create()

def login_required_if_no_ano(func):
Expand Down Expand Up @@ -103,9 +105,49 @@ def load_user_from_reverse_proxy_header(req):
rp_header_username = req.headers.get(rp_header_name)
if rp_header_username:
user = _fetch_user_by_name(rp_header_username)
if not user and config.config_reverse_proxy_create_users:
create_user_from_reverse_proxy_header(req)
user = _fetch_user_by_name(rp_header_username)

if user:
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
login_user(user)
return user
return None


def create_user_from_reverse_proxy_header(req):
rp_header_name = config.config_reverse_proxy_login_header_name
username = req.headers.get(rp_header_name)

# generate a random password
password = generate_random_password(config.config_password_min_length)
pwhash = generate_password_hash(password)

user = ub.User()
user.name = username
user.password = pwhash
user.default_language = config.config_default_language
user.locale = config.config_default_locale
user.role = config.config_default_role
user.sidebar_view = config.config_default_show
user.allowed_tags = config.config_allowed_tags
user.denied_tags = config.config_denied_tags
user.allowed_column_value = config.config_allowed_column_value
user.denied_column_value = config.config_denied_column_value

# does the user have an email address in the headers?
rp_email_header_name = config.config_reverse_proxy_email_header_name
if rp_email_header_name:
try:
user.email = check_email(req.headers.get(rp_email_header_name))
except Exception:
log.debug('No email address found in Reverse Proxy headers')

# save the user
ub.session.add(user)
try:
ub.session.commit()
except Exception as ex:
log.warning("Failed to create Reverse Proxy user: %s - %s", username, ex)
ub.session.rollback()
8 changes: 8 additions & 0 deletions messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,14 @@ msgstr ""
msgid "Reverse Proxy Header Name"
msgstr ""

#: cps/templates/admin.html:158 cps/templates/config_edit.html:178
msgid "Create Reverse Proxy Users"
msgstr ""

#: cps/templates/admin.html:162 cps/templates/config_edit.html:181
msgid "Reverse Proxy Email Header Name"
msgstr ""

#: cps/templates/admin.html:159
msgid "Edit Calibre Database Configuration"
msgstr ""
Expand Down

0 comments on commit a326949

Please sign in to comment.