FreedomBox/actions/auth-pubtkt
Joseph Nuthalapati 5e20a47bf4
sso: Increase timeout to 60 minutes
This is a temporary measure since no permanent solution was found for the cases
where auth_pubtkt cookie refresh fails when an application sends POST requests
only during the time a cookie refresh must happen. e.g. transmission and tt-rss

This at least makes the above applications usable for an hour. And then the user
must refresh the application on their web browser. This does not affect the
mobile apps.

The only other option for now is to disable SSO for the above applications till
we implement a better SSO solution which is undesirable since our users are
already used to the SSO feature.

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2018-02-10 15:00:23 -05:00

154 lines
5.1 KiB
Python
Executable File

#!/usr/bin/python3
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# 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/>.
#
"""
Module with utilities to generate a auth_pubtkt ticket and
sign it with the FreedomBox server's private key.
"""
import argparse
import base64
import datetime
from OpenSSL import crypto
import os
from plinth import action_utils
KEYS_DIRECTORY = '/etc/apache2/auth-pubtkt-keys'
def parse_arguments():
""" Return parsed command line arguments as dictionary. """
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser(
'create-key-pair',
help='create a key pair for the apache server '
'to sign auth_pubtkt tickets')
subparsers.add_parser(
'enable-mod', help='enabled the Apache module auth_pubtkt')
gen_tkt = subparsers.add_parser(
'generate-ticket', help='generate auth_pubtkt ticket')
gen_tkt.add_argument('--uid', help='username of the user')
gen_tkt.add_argument(
'--private-key-file',
help='path of the private key file of the server')
gen_tkt.add_argument(
'--tokens', help='tokens, usually containing the user groups')
subparsers.required = True
return parser.parse_args()
def subcommand_enable_mod(_):
"""Safety check to make sure auth_pubtkt is enabled"""
action_utils.webserver_enable('auth_pubtkt', kind='module')
def subcommand_create_key_pair(_):
"""Create public/private key pair for signing the auth_pubtkt
tickets.
"""
private_key_file = os.path.join(KEYS_DIRECTORY, 'privkey.pem')
public_key_file = os.path.join(KEYS_DIRECTORY, 'pubkey.pem')
os.path.exists(KEYS_DIRECTORY) or os.mkdir(KEYS_DIRECTORY)
if not all([
os.path.exists(key_file)
for key_file in [public_key_file, private_key_file]
]):
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, 4096)
with open(private_key_file, 'w') as priv_key_file:
priv_key = crypto.dump_privatekey(crypto.FILETYPE_PEM,
pkey).decode()
priv_key_file.write(priv_key)
with open(public_key_file, 'w') as pub_key_file:
pub_key = crypto.dump_publickey(crypto.FILETYPE_PEM, pkey).decode()
pub_key_file.write(pub_key)
for fil in [public_key_file, private_key_file]:
os.chmod(fil, 0o440)
def create_ticket(pkey,
uid,
validuntil,
ip=None,
tokens=None,
udata=None,
graceperiod=None,
extra_fields=None):
"""Create and return a signed mod_auth_pubtkt ticket."""
fields = [
'uid={}'.format(uid), 'validuntil={}'.format(validuntil, type='d'), ip
and 'cip={}'.format(ip), tokens and 'tokens={}'.format(tokens),
graceperiod and 'graceperiod={}'.format(graceperiod, type='d'), udata
and 'udata={}'.format(udata), extra_fields
and ';'.join(['{}={}'.format(k, v) for k, v in extra_fields])
]
data = ';'.join(filter(None, fields))
signature = 'sig={}'.format(sign(pkey, data))
return ';'.join([data, signature])
def sign(pkey, data):
"""Calculates and returns ticket's signature."""
sig = crypto.sign(pkey, data, 'sha512')
return base64.b64encode(sig).decode()
def subcommand_generate_ticket(arguments):
"""Generate a mod_auth_pubtkt ticket using login credentials."""
uid = arguments.uid
private_key_file = arguments.private_key_file
tokens = arguments.tokens
with open(private_key_file, 'r') as fil:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, fil.read().encode())
valid_until = minutes_from_now(60)
grace_period = minutes_from_now(55)
print(create_ticket(pkey, uid, valid_until, tokens=tokens,
graceperiod=grace_period))
def minutes_from_now(minutes):
"""Return a timestamp at the given number of minutes from now."""
return seconds_from_now(minutes * 60)
def seconds_from_now(seconds):
"""Return a timestamp at the given number of seconds from now."""
return (
datetime.datetime.now() + datetime.timedelta(0, seconds)).timestamp()
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()