From 67a5db9e37f0f9ee4d5f616f811944f808931a44 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Jan 08 2019 10:43:27 +0000 Subject: [PATCH 1/2] Allow marking plugins as retired This results in the plugin being ineligible to be enabled by the admin. Signed-off-by: Patrick Uiterwijk --- diff --git a/ipsilon/admin/common.py b/ipsilon/admin/common.py index f69d11f..8c177de 100644 --- a/ipsilon/admin/common.py +++ b/ipsilon/admin/common.py @@ -262,6 +262,9 @@ class AdminPlugins(AdminPage): obj = self._get_plugin_obj(plugin) except AdminError as e: return self.root_with_msg(str(e), ADMIN_STATUS_WARN) + if obj.retired: + msg = "Plugin %s has been retired" % plugin + return self.root_with_msg(msg, ADMIN_STATUS_WARN) if not obj.is_enabled: try: obj.enable() diff --git a/ipsilon/util/plugin.py b/ipsilon/util/plugin.py index 2281dcd..fc24a34 100644 --- a/ipsilon/util/plugin.py +++ b/ipsilon/util/plugin.py @@ -116,6 +116,7 @@ class PluginInstaller(PluginLoader): class PluginObject(Log): + retired = False def __init__(self, plugins=None): if plugins is None: From 740599936f4b693b6b2d54a6605e315643897be7 Mon Sep 17 00:00:00 2001 From: Patrick Uiterwijk Date: Jan 08 2019 15:09:27 +0000 Subject: [PATCH 2/2] Mark Persona as Retired If the plugin is enabled, we disable it, and we don't let the admin re-enable it. Signed-off-by: Patrick Uiterwijk --- diff --git a/contrib/fedora/ipsilon.spec b/contrib/fedora/ipsilon.spec index cb6f82f..56720ad 100644 --- a/contrib/fedora/ipsilon.spec +++ b/contrib/fedora/ipsilon.spec @@ -394,8 +394,7 @@ fi %{_datadir}/ipsilon/templates/openidc/ %files persona -%{python2_sitelib}/ipsilon/providers/persona* -%{_datadir}/ipsilon/templates/persona +%{python2_sitelib}/ipsilon/providers/personaidp.py* %files authfas %{python2_sitelib}/ipsilon/login/authfas* diff --git a/ipsilon/providers/persona/__init__.py b/ipsilon/providers/persona/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/ipsilon/providers/persona/__init__.py +++ /dev/null diff --git a/ipsilon/providers/persona/auth.py b/ipsilon/providers/persona/auth.py deleted file mode 100644 index daa64f6..0000000 --- a/ipsilon/providers/persona/auth.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING - -from ipsilon.providers.common import ProviderPageBase -from ipsilon.util.user import UserSession -from ipsilon.util.endpoint import allow_iframe - -import base64 -import cherrypy -import time -import json -import M2Crypto - - -class AuthenticateRequest(ProviderPageBase): - - def __init__(self, site, provider, *args, **kwargs): - super(AuthenticateRequest, self).__init__(site, provider) - self.trans = None - - def _preop(self, *args, **kwargs): - self.trans = self.get_valid_transaction('persona', **kwargs) - - def pre_GET(self, *args, **kwargs): - self._preop(*args, **kwargs) - - def pre_POST(self, *args, **kwargs): - self._preop(*args, **kwargs) - - -class Sign(AuthenticateRequest): - - def _base64_url_decode(self, inp): - inp += '=' * (4 - (len(inp) % 4)) - return base64.urlsafe_b64decode(inp) - - def _base64_url_encode(self, inp): - return base64.urlsafe_b64encode(inp).replace('=', '') - - def _persona_sign(self, email, publicKey, certDuration): - self.debug('Signing for %s with duration of %s' % (email, - certDuration)) - header = {'alg': 'RS256'} - header = json.dumps(header) - header = self._base64_url_encode(header) - - claim = {} - # Valid from 10 seconds before now to account for clock skew - claim['iat'] = 1000 * int(time.time() - 10) - # Validity of at most 24 hours - claim['exp'] = 1000 * int(time.time() + - min(certDuration, 24 * 60 * 60)) - - claim['iss'] = self.cfg.issuer_domain - claim['public-key'] = json.loads(publicKey) - claim['principal'] = {'email': email} - - claim = json.dumps(claim) - claim = self._base64_url_encode(claim) - - certificate = '%s.%s' % (header, claim) - digest = M2Crypto.EVP.MessageDigest('sha256') - digest.update(certificate) - signature = self.cfg.key.sign(digest.digest(), 'sha256') - signature = self._base64_url_encode(signature) - signed_certificate = '%s.%s' % (certificate, signature) - - return signed_certificate - - def _willing_to_sign(self, email, username): - for domain in self.cfg.allowed_domains: - if email == ('%s@%s' % (username, domain)): - return True - return False - - @allow_iframe - def POST(self, *args, **kwargs): - if 'email' not in kwargs or 'publicKey' not in kwargs \ - or 'certDuration' not in kwargs or '@' not in kwargs['email']: - cherrypy.response.status = 400 - raise Exception('Invalid request: %s' % kwargs) - - us = UserSession() - user = us.get_user() - - if user.is_anonymous: - raise cherrypy.HTTPError(401, 'Not signed in') - - if not self._willing_to_sign(kwargs['email'], user.name): - self.log('Not willing to sign for %s, logged in as %s' % ( - kwargs['email'], user.name)) - raise cherrypy.HTTPError(403, 'Incorrect user') - - return self._persona_sign(kwargs['email'], kwargs['publicKey'], - kwargs['certDuration']) - - -class SignInResult(AuthenticateRequest): - @allow_iframe - def GET(self, *args, **kwargs): - user = UserSession().get_user() - - return self._template('persona/signin_result.html', - loggedin=not user.is_anonymous) - - -class SignIn(AuthenticateRequest): - def __init__(self, *args, **kwargs): - super(SignIn, self).__init__(*args, **kwargs) - self.result = SignInResult(*args, **kwargs) - self.trans = None - - @allow_iframe - def GET(self, *args, **kwargs): - username = None - domain = None - if 'email' in kwargs: - if '@' in kwargs['email']: - username, domain = kwargs['email'].split('@', 2) - self.debug('Persona SignIn requested for: %s@%s' % (username, - domain)) - - returl = '%s/persona/SignIn/result?%s' % ( - self.basepath, self.trans.get_GET_arg()) - data = {'login_return': returl, - 'login_target': 'Persona', - 'login_username': username} - self.trans.store(data) - redirect = '%s/login?%s' % (self.basepath, - self.trans.get_GET_arg()) - self.debug('Redirecting: %s' % redirect) - raise cherrypy.HTTPRedirect(redirect) - - -class Persona(AuthenticateRequest): - - def __init__(self, *args, **kwargs): - super(Persona, self).__init__(*args, **kwargs) - self.Sign = Sign(*args, **kwargs) - self.SignIn = SignIn(*args, **kwargs) - self.trans = None - - @allow_iframe - def GET(self, *args, **kwargs): - user = UserSession().get_user() - return self._template('persona/provisioning.html', - loggedin=not user.is_anonymous) diff --git a/ipsilon/providers/personaidp.py b/ipsilon/providers/personaidp.py index 265847a..a74da56 100644 --- a/ipsilon/providers/personaidp.py +++ b/ipsilon/providers/personaidp.py @@ -3,75 +3,32 @@ from __future__ import absolute_import from ipsilon.providers.common import ProviderBase, ProviderInstaller -from ipsilon.util.plugin import PluginObject -from ipsilon.util import config as pconfig -from ipsilon.info.common import InfoMapping -from ipsilon.providers.persona.auth import Persona -from ipsilon.tools import files - -import json -import M2Crypto -import os class IdpProvider(ProviderBase): + retired = True def __init__(self, *pargs): - super(IdpProvider, self).__init__('persona', 'Persona', 'persona', - *pargs) - self.mapping = InfoMapping() - self.page = None - self.basepath = None - self.key = None - self.key_info = None + super(IdpProvider, self).__init__('persona', 'Persona (RETIRED)', + 'persona', *pargs) self.description = """ -Provides Persona authentication infrastructure. """ +RETIRED Provided Persona authentication infrastructure. """ self.new_config( self.name, - pconfig.String( - 'issuer domain', - 'The issuer domain of the Persona provider', - 'localhost'), - pconfig.String( - 'idp key file', - 'The key where the Persona key is stored.', - 'persona.key'), - pconfig.List( - 'allowed domains', - 'List of domains this IdP is willing to issue claims for.'), ) - @property - def issuer_domain(self): - return self.get_config_value('issuer domain') - - @property - def idp_key_file(self): - return self.get_config_value('idp key file') + def register(self, root, site): + pass - @property - def allowed_domains(self): - return self.get_config_value('allowed domains') + def on_enable(self): + self.disable() def get_tree(self, site): - self.page = Persona(site, self) - # self.admin = AdminPage(site, self) + raise NotImplementedError("No tree available for Persona") - return self.page - - def init_idp(self): - # Init IDP data - try: - self.key = M2Crypto.RSA.load_key(self.idp_key_file, - lambda *args: None) - except Exception as e: # pylint: disable=broad-except - self.debug('Failed to init Persona provider: %r' % e) - return None - - def on_enable(self): - super(IdpProvider, self).on_enable() - self.init_idp() + def on_disable(self): + pass def get_client_display_name(self, clientid): return clientid @@ -88,48 +45,7 @@ class Installer(ProviderInstaller): self.pargs = pargs def install_args(self, group): - group.add_argument('--persona', choices=['yes', 'no'], default='yes', - help='Configure Persona Provider') + pass def configure(self, opts, changes): - if opts['persona'] != 'yes': - return - - # Check storage path is present or create it - path = os.path.join(opts['data_dir'], 'persona') - if not os.path.exists(path): - os.makedirs(path, 0o700) - - keyfile = os.path.join(path, 'persona.key') - exponent = 0x10001 - key = M2Crypto.RSA.gen_key(2048, exponent) - key.save_key(keyfile, cipher=None) - key_n = 0 - for c in key.n[4:]: - key_n = (key_n*256) + ord(c) - wellknown = dict() - wellknown['authentication'] = ('%s/persona/SignIn/' - % opts['instanceurl']) - wellknown['provisioning'] = '%s/persona/' % opts['instanceurl'] - wellknown['public-key'] = {'algorithm': 'RS', - 'e': str(exponent), - 'n': str(key_n)} - with open(os.path.join(opts['wellknown_dir'], 'browserid'), 'w') as f: - f.write(json.dumps(wellknown)) - - # Add configuration data to database - po = PluginObject(*self.pargs) - po.name = 'persona' - po.wipe_data() - po.wipe_config_values() - config = {'issuer domain': opts['hostname'], - 'idp key file': keyfile, - 'allowed domains': opts['hostname']} - po.save_plugin_config(config) - - # Update global config to add login plugin - po.is_enabled = True - po.save_enabled_state() - - # Fixup permissions so only the ipsilon user can read these files - files.fix_user_dirs(path, opts['system_user']) + pass diff --git a/man/ipsilon-server-install.1 b/man/ipsilon-server-install.1 index f91e93d..703067e 100644 --- a/man/ipsilon-server-install.1 +++ b/man/ipsilon-server-install.1 @@ -87,9 +87,6 @@ Configure OpenID Provider \fB\-\-openid\-dburi\fR \fIOPENID_DBURI\fR OpenID database URI (override template) .TP -\fB\-\-persona\fR -Configure Persona Provider -.TP \fB\-\-saml2\fR Configure SAML2 Provider .TP diff --git a/setup.py b/setup.py index ca09217..745854a 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ setup( 'ipsilon.providers.openid.extensions', 'ipsilon.providers.openidc', 'ipsilon.providers.openidc.plugins', - 'ipsilon.providers.persona', 'ipsilon.authz', 'ipsilon.user', 'ipsilon.tools', 'ipsilon.helpers', 'tests', 'tests.helpers'], @@ -59,7 +58,6 @@ setup( (DATA+'templates/saml2', glob('templates/saml2/*.html')), (DATA+'templates/openid', glob('templates/openid/*')), (DATA+'templates/openidc', glob('templates/openidc/*')), - (DATA+'templates/persona', glob('templates/persona/*.html')), (DATA+'templates/install', glob('templates/install/*.conf')), (DATA+'templates/install/openidc', glob('templates/install/openidc/*.conf')), diff --git a/templates/persona/provisioning.html b/templates/persona/provisioning.html deleted file mode 100644 index a693cac..0000000 --- a/templates/persona/provisioning.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends "master.html" %} -{% block main %} -
-
-

This page is used internally

-
-
- - - -{% endblock %} diff --git a/templates/persona/signin_result.html b/templates/persona/signin_result.html deleted file mode 100644 index cda130d..0000000 --- a/templates/persona/signin_result.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "master.html" %} -{% block main %} -
-
-

This page is used internally

-
-
- - - -{% endblock %}