diff options
-rwxr-xr-x | ipsilon/admin/common.py | 74 | ||||
-rwxr-xr-x | ipsilon/info/common.py | 11 | ||||
-rwxr-xr-x | ipsilon/info/infoldap.py | 55 | ||||
-rwxr-xr-x | ipsilon/info/nss.py | 1 | ||||
-rwxr-xr-x | ipsilon/login/authfas.py | 63 | ||||
-rwxr-xr-x | ipsilon/login/authform.py | 34 | ||||
-rwxr-xr-x | ipsilon/login/authkrb.py | 1 | ||||
-rwxr-xr-x | ipsilon/login/authldap.py | 74 | ||||
-rwxr-xr-x | ipsilon/login/authpam.py | 44 | ||||
-rwxr-xr-x | ipsilon/login/authtest.py | 36 | ||||
-rwxr-xr-x | ipsilon/login/common.py | 11 | ||||
-rwxr-xr-x | ipsilon/providers/common.py | 17 | ||||
-rwxr-xr-x | ipsilon/providers/openid/auth.py | 4 | ||||
-rwxr-xr-x | ipsilon/providers/openid/extensions/common.py | 10 | ||||
-rwxr-xr-x | ipsilon/providers/openid/meta.py | 4 | ||||
-rwxr-xr-x | ipsilon/providers/openidp.py | 69 | ||||
-rwxr-xr-x | ipsilon/providers/saml2idp.py | 84 | ||||
-rwxr-xr-x | ipsilon/util/config.py | 51 | ||||
-rwxr-xr-x | ipsilon/util/plugin.py | 107 | ||||
-rw-r--r-- | templates/admin/plugin_config.html | 61 | ||||
-rw-r--r-- | templates/admin/plugins.html | 4 | ||||
-rw-r--r-- | templates/admin/providers.html | 4 |
22 files changed, 401 insertions, 418 deletions
diff --git a/ipsilon/admin/common.py b/ipsilon/admin/common.py index b9dfbf4..3d5313a 100755 --- a/ipsilon/admin/common.py +++ b/ipsilon/admin/common.py @@ -21,6 +21,7 @@ import cherrypy from ipsilon.util.page import Page from ipsilon.util.page import admin_protect from ipsilon.util.plugin import PluginObject +from ipsilon.util import config as pconfig class AdminPage(Page): @@ -46,33 +47,13 @@ class AdminPluginConfig(AdminPage): self.menu = [parent] self.back = parent.url - # Get the defaults - options = po.get_config_desc() - if options is None: - options = dict() - - self.options_order = [] - if hasattr(po, 'conf_opt_order'): - self.options_order = po.conf_opt_order - - # append any undefined options - add = [] - for k in options.keys(): - if k not in self.options_order: - add.append(k) - if len(add): - add.sort() - for k in add: - self.options_order.append(k) - def root_with_msg(self, message=None, message_type=None): return self._template('admin/plugin_config.html', title=self.title, menu=self.menu, action=self.url, back=self.back, message=message, message_type=message_type, name='admin_%s_%s_form' % (self.facility, self._po.name), - options_order=self.options_order, - plugin=self._po) + config=self._po.get_config_obj()) @admin_protect def GET(self, *args, **kwargs): @@ -83,31 +64,46 @@ class AdminPluginConfig(AdminPage): message = "Nothing was modified." message_type = "info" - new_values = dict() - - # Get the defaults - options = self._po.get_config_desc() - if options is None: - options = dict() - - for key, value in kwargs.iteritems(): - if key in options: - if value != self._po.get_config_value(key): - cherrypy.log.error("Storing [%s]: %s = %s" % - (self._po.name, key, value)) - new_values[key] = value - - if len(new_values) != 0: + new_db_values = dict() + + conf = self._po.get_config_obj() + + for name, option in conf.iteritems(): + if name in kwargs: + value = kwargs[name] + if isinstance(option, pconfig.List): + value = [x.strip() for x in value.split('\n')] + elif isinstance(option, pconfig.Condition): + value = True + else: + if isinstance(option, pconfig.Condition): + value = False + elif isinstance(option, pconfig.Choice): + value = list() + for a in option.get_allowed(): + aname = '%s_%s' % (name, a) + if aname in kwargs: + value.append(a) + else: + continue + + if value != option.get_value(): + cherrypy.log.error("Storing [%s]: %s = %s" % + (self._po.name, name, value)) + option.set_value(value) + new_db_values[name] = option.export_value() + + if len(new_db_values) != 0: # First we try to save in the database try: - self._po.save_plugin_config(self.facility, new_values) + self._po.save_plugin_config(self.facility, new_db_values) message = "New configuration saved." message_type = "success" except Exception: # pylint: disable=broad-except message = "Failed to save data!" message_type = "error" - # And only if it succeeds we change the live object + # Then refresh the actual objects self._po.refresh_plugin_config(self.facility) return self.root_with_msg(message=message, @@ -210,7 +206,7 @@ class AdminPlugins(AdminPage): po.name = "global" globalconf = dict() globalconf['order'] = ','.join(names) - po.set_config(globalconf) + po.import_config(globalconf) po.save_plugin_config(self.facility) def reorder_plugins(self, names): diff --git a/ipsilon/info/common.py b/ipsilon/info/common.py index 03de66a..586b9e5 100755 --- a/ipsilon/info/common.py +++ b/ipsilon/info/common.py @@ -5,14 +5,15 @@ # See the file named COPYING for the project license from ipsilon.util.log import Log -from ipsilon.util.plugin import PluginLoader, PluginObject -from ipsilon.util.plugin import PluginInstaller +from ipsilon.util.plugin import PluginInstaller, PluginLoader +from ipsilon.util.plugin import PluginObject, PluginConfig -class InfoProviderBase(PluginObject, Log): +class InfoProviderBase(PluginConfig, PluginObject): def __init__(self): - super(InfoProviderBase, self).__init__() + PluginConfig.__init__(self) + PluginObject.__init__(self) self._site = None self.is_enabled = False @@ -29,7 +30,7 @@ class InfoProviderBase(PluginObject, Log): # configure self if self.name in plugins['config']: - self.set_config(plugins['config'][self.name]) + self.import_config(plugins['config'][self.name]) plugins['enabled'].append(self) self.is_enabled = True diff --git a/ipsilon/info/infoldap.py b/ipsilon/info/infoldap.py index 70d36d5..369d3f1 100755 --- a/ipsilon/info/infoldap.py +++ b/ipsilon/info/infoldap.py @@ -8,7 +8,7 @@ from ipsilon.info.common import InfoProviderBase from ipsilon.info.common import InfoProviderInstaller from ipsilon.info.common import InfoMapping from ipsilon.util.plugin import PluginObject -from ipsilon.util.log import Log +from ipsilon.util import config as pconfig import ldap @@ -27,7 +27,7 @@ ldap_mapping = { } -class InfoProvider(InfoProviderBase, Log): +class InfoProvider(InfoProviderBase): def __init__(self): super(InfoProvider, self).__init__() @@ -36,34 +36,29 @@ class InfoProvider(InfoProviderBase, Log): self.name = 'ldap' self.description = """ Info plugin that uses LDAP to retrieve user data. """ - self._options = { - 'server url': [ - """ The LDAP server url """, - 'string', - 'ldap://example.com' - ], - 'tls': [ - " What TLS level show be required " + - "(Demand, Allow, Try, Never, NoTLS) ", - 'string', - 'Demand' - ], - 'bind dn': [ - """ User DN to bind as, if empty uses anonymous bind. """, - 'string', - 'uid=ipsilon,ou=People,dc=example,dc=com' - ], - 'bind password': [ - """ Password to use for bind operation """, - 'string', - 'Password' - ], - 'user dn template': [ - """ Template to turn username into DN. """, - 'string', - 'uid=%(username)s,ou=People,dc=example,dc=com' - ], - } + self.new_config( + self.name, + pconfig.String( + 'server url', + 'The LDAP server url.', + 'ldap://example.com'), + pconfig.Template( + 'user dn template', + 'Template to turn username into DN.', + 'uid=%(username)s,ou=People,dc=example,dc=com'), + pconfig.Pick( + 'tls', + 'What TLS level show be required', + ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'], + 'Demand'), + pconfig.String( + 'bind dn', + 'DN to bind as, if empty uses anonymous bind.', + 'uid=ipsilon,ou=People,dc=example,dc=com'), + pconfig.String( + 'bind password', + 'Password to use for bind operation'), + ) @property def server_url(self): diff --git a/ipsilon/info/nss.py b/ipsilon/info/nss.py index 24f3522..3dfd885 100755 --- a/ipsilon/info/nss.py +++ b/ipsilon/info/nss.py @@ -25,6 +25,7 @@ class InfoProvider(InfoProviderBase): self.mapper = InfoMapping() self.mapper.set_mapping(posix_map) self.name = 'nss' + self.new_config(self.name) def _get_posix_user(self, user): p = pwd.getpwnam(user) diff --git a/ipsilon/login/authfas.py b/ipsilon/login/authfas.py index c2d8fff..71db372 100755 --- a/ipsilon/login/authfas.py +++ b/ipsilon/login/authfas.py @@ -7,6 +7,7 @@ from ipsilon.info.common import InfoMapping from ipsilon.login.common import LoginFormBase, LoginManagerBase from ipsilon.login.common import FACILITY from ipsilon.util.plugin import PluginObject +from ipsilon.util import config as pconfig import cherrypy from fedora.client.fasproxy import FasProxyClient @@ -112,41 +113,33 @@ class LoginManager(LoginManagerBase): self.description = """ Form based login Manager that uses the Fedora Authentication Server """ - self._options = { - 'help text': [ - """ The text shown to guide the user at login time. """, - 'string', - 'Login wth your FAS credentials' - ], - 'username text': [ - """ The text shown to ask for the username in the form. """, - 'string', - 'FAS Username' - ], - 'password text': [ - """ The text shown to ask for the password in the form. """, - 'string', - 'Password' - ], - 'FAS url': [ - """ The FAS Url. """, - 'string', - 'https://admin.fedoraproject.org/accounts/' - ], - 'FAS Proxy client user Agent': [ - """ The User Agent presented to the FAS Server. """, - 'string', - 'Ipsilon v1.0' - ], - 'FAS Insecure Auth': [ - """ If 'YES' skips FAS server cert verification. """, - 'string', - '' - ], - } - self.conf_opt_order = ['FAS url', 'FAS Proxy client user Agent', - 'FAS Insecure Auth', 'username text', - 'password text', 'help text'] + self.new_config( + self.name, + pconfig.String( + 'FAS url', + 'The FAS Url.', + 'https://admin.fedoraproject.org/accounts/'), + pconfig.String( + 'FAS Proxy client user Agent', + 'The User Agent presented to the FAS Server.', + 'Ipsilon v1.0'), + pconfig.Condition( + 'FAS Insecure Auth', + 'If checked skips FAS server cert verification.', + False), + pconfig.String( + 'username text', + 'Text used to ask for the username at login time.', + 'FAS Username'), + pconfig.String( + 'password text', + 'Text used to ask for the password at login time.', + 'Password'), + pconfig.String( + 'help text', + 'Text used to guide the user at login time.', + 'Login with your FAS credentials') + ) @property def help_text(self): diff --git a/ipsilon/login/authform.py b/ipsilon/login/authform.py index 418a5e5..4e9f5c1 100755 --- a/ipsilon/login/authform.py +++ b/ipsilon/login/authform.py @@ -21,6 +21,7 @@ from ipsilon.login.common import LoginFormBase, LoginManagerBase from ipsilon.login.common import FACILITY from ipsilon.util.plugin import PluginObject from ipsilon.util.user import UserSession +from ipsilon.util import config as pconfig from string import Template import cherrypy import subprocess @@ -54,24 +55,21 @@ class LoginManager(LoginManagerBase): self.description = """ Form based login Manager. Relies on mod_intercept_form_submit plugin for actual authentication. """ - self._options = { - 'help text': [ - """ The text shown to guide the user at login time. """, - 'string', - 'Insert your Username and Password and then submit.' - ], - 'username text': [ - """ The text shown to ask for the username in the form. """, - 'string', - 'Username' - ], - 'password text': [ - """ The text shown to ask for the password in the form. """, - 'string', - 'Password' - ], - } - self.conf_opt_order = ['username text', 'password text', 'help text'] + self.new_config( + self.name, + pconfig.String( + 'username text', + 'Text used to ask for the username at login time.', + 'Username'), + pconfig.String( + 'password text', + 'Text used to ask for the password at login time.', + 'Password'), + pconfig.String( + 'help text', + 'Text used to guide the user at login time.', + 'Insert your Username and Password and then submit.') + ) @property def help_text(self): diff --git a/ipsilon/login/authkrb.py b/ipsilon/login/authkrb.py index 6c561ac..f2af0a0 100755 --- a/ipsilon/login/authkrb.py +++ b/ipsilon/login/authkrb.py @@ -84,6 +84,7 @@ class LoginManager(LoginManagerBase): self.description = """ Kereros Negotiate authentication plugin. Relies on the mod_auth_kerb apache plugin for actual authentication. """ + self.new_config(self.name) def get_tree(self, site): self.page = Krb(site, self) diff --git a/ipsilon/login/authldap.py b/ipsilon/login/authldap.py index ed75e91..f51f375 100755 --- a/ipsilon/login/authldap.py +++ b/ipsilon/login/authldap.py @@ -6,6 +6,7 @@ from ipsilon.login.common import LoginFormBase, LoginManagerBase from ipsilon.login.common import FACILITY from ipsilon.util.plugin import PluginObject from ipsilon.util.log import Log +from ipsilon.util import config as pconfig from ipsilon.info.infoldap import InfoProvider as LDAPInfo import ldap @@ -107,47 +108,38 @@ class LoginManager(LoginManagerBase): self.description = """ Form based login Manager that uses a simple bind LDAP operation to perform authentication. """ - self._options = { - 'help text': [ - """ The text shown to guide the user at login time. """, - 'string', - 'Insert your Username and Password and then submit.' - ], - 'username text': [ - """ The text shown to ask for the username in the form. """, - 'string', - 'Username' - ], - 'password text': [ - """ The text shown to ask for the password in the form. """, - 'string', - 'Password' - ], - 'server url': [ - """ The LDAP server url """, - 'string', - 'ldap://example.com' - ], - 'tls': [ - " What TLS level show be required " + - "(Demand, Allow, Try, Never, NoTLS) ", - 'string', - 'Demand' - ], - 'bind dn template': [ - """ Template to turn username into DN. """, - 'string', - 'uid=%(username)s,ou=People,dc=example,dc=com' - ], - 'get user info': [ - """ Get user info via ldap directly after auth (Yes/No) """, - 'string', - 'Yes' - ], - } - self.conf_opt_order = ['server url', 'bind dn template', - 'get user info', 'tls', 'username text', - 'password text', 'help text'] + self.new_config( + self.name, + pconfig.String( + 'server url', + 'The LDAP server url.', + 'ldap://example.com'), + pconfig.Template( + 'bind dn template', + 'Template to turn username into DN.', + 'uid=%(username)s,ou=People,dc=example,dc=com'), + pconfig.Condition( + 'get user info', + 'Get user info via ldap using user credentials', + True), + pconfig.Pick( + 'tls', + 'What TLS level show be required', + ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'], + 'Demand'), + pconfig.String( + 'username text', + 'Text used to ask for the username at login time.', + 'Username'), + pconfig.String( + 'password text', + 'Text used to ask for the password at login time.', + 'Password'), + pconfig.String( + 'help text', + 'Text used to guide the user at login time.', + 'Provide your Username and Password') + ) @property def help_text(self): diff --git a/ipsilon/login/authpam.py b/ipsilon/login/authpam.py index 10b550e..c7cb9a0 100755 --- a/ipsilon/login/authpam.py +++ b/ipsilon/login/authpam.py @@ -20,6 +20,7 @@ from ipsilon.login.common import LoginFormBase, LoginManagerBase from ipsilon.login.common import FACILITY from ipsilon.util.plugin import PluginObject +from ipsilon.util import config as pconfig import pam import subprocess @@ -76,30 +77,25 @@ class LoginManager(LoginManagerBase): self.description = """ Form based login Manager that uses the system's PAM infrastructure for authentication. """ - self._options = { - 'service name': [ - """ The name of the PAM service used to authenticate. """, - 'string', - 'remote' - ], - 'help text': [ - """ The text shown to guide the user at login time. """, - 'string', - 'Insert your Username and Password and then submit.' - ], - 'username text': [ - """ The text shown to ask for the username in the form. """, - 'string', - 'Username' - ], - 'password text': [ - """ The text shown to ask for the password in the form. """, - 'string', - 'Password' - ], - } - self.conf_opt_order = ['service name', 'username text', - 'password text', 'help text'] + self.new_config( + self.name, + pconfig.String( + 'service name', + 'The name of the PAM service used to authenticate.', + 'remote'), + pconfig.String( + 'username text', + 'Text used to ask for the username at login time.', + 'Username'), + pconfig.String( + 'password text', + 'Text used to ask for the password at login time.', + 'Password'), + pconfig.String( + 'help text', + 'Text used to guide the user at login time.', + 'Provide your Username and Password') + ) @property def service_name(self): diff --git a/ipsilon/login/authtest.py b/ipsilon/login/authtest.py index 6288826..e3f8eff 100755 --- a/ipsilon/login/authtest.py +++ b/ipsilon/login/authtest.py @@ -20,6 +20,7 @@ from ipsilon.login.common import LoginFormBase, LoginManagerBase from ipsilon.login.common import FACILITY from ipsilon.util.plugin import PluginObject +from ipsilon.util import config as pconfig import cherrypy @@ -63,23 +64,24 @@ class LoginManager(LoginManagerBase): self.page = None self.description = """ Form based TEST login Manager, DO NOT EVER ACTIVATE IN PRODUCTION """ - self._options = { - 'help text': [ - """ The text shown to guide the user at login time. """, - 'string', - 'Insert your Username and Password and then submit.' - ], - 'username text': [ - """ The text shown to ask for the username in the form. """, - 'string', - 'Username' - ], - 'password text': [ - """ The text shown to ask for the password in the form. """, - 'string', - 'Password' - ], - } + self.new_config( + self.name, + pconfig.String( + 'username text', + 'Text used to ask for the username at login time.', + 'Username'), + pconfig.String( + 'password text', + 'Text used to ask for the password at login time.', + 'Password'), + pconfig.String( + 'help text', + 'Text used to guide the user at login time.', + 'DISABLE IN PRODUCTION, USE ONLY FOR TEST ' + + 'Use any username they are all valid, "admin" gives ' + + 'administrative powers. ' + + 'Use the fixed password "ipsilon" for any user') + ) @property def help_text(self): diff --git a/ipsilon/login/common.py b/ipsilon/login/common.py index 028b754..ad09ce1 100755 --- a/ipsilon/login/common.py +++ b/ipsilon/login/common.py @@ -19,8 +19,8 @@ from ipsilon.util.page import Page from ipsilon.util.user import UserSession -from ipsilon.util.plugin import PluginLoader, PluginObject -from ipsilon.util.plugin import PluginInstaller +from ipsilon.util.plugin import PluginInstaller, PluginLoader +from ipsilon.util.plugin import PluginObject, PluginConfig from ipsilon.info.common import Info from ipsilon.util.cookies import SecureCookie import cherrypy @@ -29,10 +29,11 @@ import cherrypy USERNAME_COOKIE = 'ipsilon_default_username' -class LoginManagerBase(PluginObject): +class LoginManagerBase(PluginConfig, PluginObject): def __init__(self): - super(LoginManagerBase, self).__init__() + PluginConfig.__init__(self) + PluginObject.__init__(self) self._site = None self.path = '/' self.next_login = None @@ -126,7 +127,7 @@ class LoginManagerBase(PluginObject): # configure self if self.name in plugins['config']: - self.set_config(plugins['config'][self.name]) + self.import_config(plugins['config'][self.name]) # and add self to the root root = plugins['root'] diff --git a/ipsilon/providers/common.py b/ipsilon/providers/common.py index d882b40..ead50e2 100755 --- a/ipsilon/providers/common.py +++ b/ipsilon/providers/common.py @@ -18,8 +18,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from ipsilon.util.log import Log -from ipsilon.util.plugin import PluginLoader, PluginObject -from ipsilon.util.plugin import PluginInstaller +from ipsilon.util.plugin import PluginInstaller, PluginLoader +from ipsilon.util.plugin import PluginObject, PluginConfig from ipsilon.util.page import Page import cherrypy @@ -49,10 +49,11 @@ class InvalidRequest(ProviderException): self._debug(message) -class ProviderBase(PluginObject): +class ProviderBase(PluginConfig, PluginObject): def __init__(self, name, path): - super(ProviderBase, self).__init__() + PluginConfig.__init__(self) + PluginObject.__init__(self) self.name = name self.path = path self.tree = None @@ -74,14 +75,14 @@ class ProviderBase(PluginObject): # configure self plugins = site[FACILITY] if self.name in plugins['config']: - self.set_config(plugins['config'][self.name]) + self.import_config(plugins['config'][self.name]) # init pages and admin interfaces self.tree = self.get_tree(site) self._debug('IdP Provider registered: %s' % self.name) - if self.get_config_value('enabled') == '1': + if self.get_config_value('enabled') is True: # and enable self self._enable(site) @@ -97,7 +98,7 @@ class ProviderBase(PluginObject): return self._enable(site) - self.set_config_value('enabled', '1') + self.set_config_value('enabled', True) self.save_plugin_config(FACILITY) def disable(self, site): @@ -109,7 +110,7 @@ class ProviderBase(PluginObject): root.del_subtree(self.name) self.is_enabled = False - self.set_config_value('enabled', '0') + self.set_config_value('enabled', False) self.save_plugin_config(FACILITY) self._debug('IdP Provider disabled: %s' % self.name) diff --git a/ipsilon/providers/openid/auth.py b/ipsilon/providers/openid/auth.py index da110f7..fba8d10 100755 --- a/ipsilon/providers/openid/auth.py +++ b/ipsilon/providers/openid/auth.py @@ -168,7 +168,7 @@ class AuthenticateRequest(ProviderPageBase): "Trust Root": request.trust_root, } userattrs = us.get_user_attrs() - for n, e in self.cfg.extensions.items(): + for n, e in self.cfg.extensions.available().items(): data = e.get_display_data(request, userattrs) self.debug('%s returned %s' % (n, repr(data))) for key, value in data.items(): @@ -194,7 +194,7 @@ class AuthenticateRequest(ProviderPageBase): claimed_id=identity_url ) userattrs = session.get_user_attrs() - for _, e in self.cfg.extensions.items(): + for _, e in self.cfg.extensions.available().items(): resp = e.get_response(request, userattrs) if resp is not None: response.addExtension(resp) diff --git a/ipsilon/providers/openid/extensions/common.py b/ipsilon/providers/openid/extensions/common.py index b75d394..804f695 100755 --- a/ipsilon/providers/openid/extensions/common.py +++ b/ipsilon/providers/openid/extensions/common.py @@ -49,13 +49,14 @@ FACILITY = 'openid_extensions' class LoadExtensions(Log): - def __init__(self, enabled): + def __init__(self): loader = PluginLoader(LoadExtensions, FACILITY, 'OpenidExtension') self.plugins = loader.get_plugin_data() available = self.plugins['available'].keys() self._debug('Available Extensions: %s' % str(available)) + def enable(self, enabled): for item in enabled: if item not in self.plugins['available']: self.debug('<%s> not available' % item) @@ -63,5 +64,8 @@ class LoadExtensions(Log): self.debug('Enable OpenId extension: %s' % item) self.plugins['available'][item].enable() - def get_extensions(self): - return self.plugins['available'] + def available(self): + available = self.plugins['available'] + if available is None: + available = dict() + return available diff --git a/ipsilon/providers/openid/meta.py b/ipsilon/providers/openid/meta.py index a04a78c..ea79439 100755 --- a/ipsilon/providers/openid/meta.py +++ b/ipsilon/providers/openid/meta.py @@ -42,7 +42,7 @@ class XRDSHandler(MetaHandler): 'http://specs.openid.net/auth/2.0/server', 'http://openid.net/server/1.0', ] - for _, e in self.cfg.extensions.items(): + for _, e in self.cfg.extensions.available().items(): types.extend(e.get_type_uris()) return self.reply(types=types, @@ -65,7 +65,7 @@ class UserXRDSHandler(XRDSHandler): 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/signon/1.0', ] - for _, e in self.cfg.extensions.items(): + for _, e in self.cfg.extensions.available().items(): types.extend(e.get_type_uris()) return self.reply(types=types, diff --git a/ipsilon/providers/openidp.py b/ipsilon/providers/openidp.py index 5abdcad..197b1cf 100755 --- a/ipsilon/providers/openidp.py +++ b/ipsilon/providers/openidp.py @@ -9,6 +9,7 @@ from ipsilon.providers.common import FACILITY from ipsilon.providers.openid.auth import OpenID from ipsilon.providers.openid.extensions.common import LoadExtensions from ipsilon.util.plugin import PluginObject +from ipsilon.util import config as pconfig from ipsilon.info.common import InfoMapping from openid.server.server import Server @@ -24,42 +25,41 @@ class IdpProvider(ProviderBase): self.page = None self.server = None self.basepath = None - self.extensions = None + self.extensions = LoadExtensions() + print self.extensions.available() + print self.extensions.available().keys() self.description = """ Provides OpenID 2.0 authentication infrastructure. """ - self._options = { - 'default email domain': [ - """Default email domain, for users missing email property.""", - 'string', - 'example.com' - ], - 'endpoint url': [ - """The Absolute URL of the OpenID provider""", - 'string', - 'http://localhost:8080/idp/openid/' - ], - 'identity url template': [ - """The templated URL where identities are exposed.""", - 'string', - 'http://localhost:8080/idp/openid/id/%(username)s' - ], - 'trusted roots': [ - """List of trusted relying parties.""", - 'list', - [] - ], - 'untrusted roots': [ - """List of untrusted relying parties.""", - 'list', - [] - ], - 'enabled extensions': [ - """List of enabled extensions""", - 'list', - [] - ], - } + self.new_config( + self.name, + pconfig.String( + 'default email domain', + 'Used for users missing the email property.', + 'example.com'), + pconfig.String( + 'endpoint url', + 'The Absolute URL of the OpenID provider', + 'http://localhost:8080/idp/openid/'), + pconfig.Template( + 'identity url template', + 'The templated URL where identities are exposed.', + 'http://localhost:8080/idp/openid/id/%(username)s'), + pconfig.List( + 'trusted roots', + 'List of trusted relying parties.'), + pconfig.List( + 'untrusted roots', + 'List of untrusted relying parties.'), + pconfig.Choice( + 'enabled extensions', + 'Choose the extensions to enable', + self.extensions.available().keys()), + pconfig.Condition( + 'enabled', + 'Whether the OpenID IDP is enabled', + False) + ) @property def endpoint_url(self): @@ -112,11 +112,10 @@ Provides OpenID 2.0 authentication infrastructure. """ def init_idp(self): self.server = Server(MemoryStore(), op_endpoint=self.endpoint_url) - loader = LoadExtensions(self.enabled_extensions) - self.extensions = loader.get_extensions() def on_enable(self): self.init_idp() + self.extensions.enable(self._config['enabled extensions'].get_value()) class Installer(object): diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py index cb2c4a2..8896e16 100755 --- a/ipsilon/providers/saml2idp.py +++ b/ipsilon/providers/saml2idp.py @@ -27,6 +27,7 @@ from ipsilon.tools import saml2metadata as metadata from ipsilon.tools import files from ipsilon.util.user import UserSession from ipsilon.util.plugin import PluginObject +from ipsilon.util import config as pconfig import cherrypy import lasso import os @@ -126,48 +127,47 @@ class IdpProvider(ProviderBase): self.description = """ Provides SAML 2.0 authentication infrastructure. """ - self._options = { - 'idp storage path': [ - """ Path to data storage accessible by the IdP """, - 'string', - '/var/lib/ipsilon/saml2' - ], - 'idp metadata file': [ - """ The IdP Metadata file genearated at install time. """, - 'string', - 'metadata.xml' - ], - 'idp certificate file': [ - """ The IdP PEM Certificate genearated at install time. """, - 'string', - 'certificate.pem' - ], - 'idp key file': [ - """ The IdP Certificate Key genearated at install time. """, - 'string', - 'certificate.key' - ], - 'allow self registration': [ - """ Allow authenticated users to register applications. """, - 'boolean', - True - ], - 'default allowed nameids': [ - """Default Allowed NameIDs for Service Providers. """, - 'list', - ['persistent', 'transient', 'email', 'kerberos', 'x509'] - ], - 'default nameid': [ - """Default NameID used by Service Providers. """, - 'string', - 'persistent' - ], - 'default email domain': [ - """Default email domain, for users missing email property.""", - 'string', - 'example.com' - ] - } + self.new_config( + self.name, + pconfig.String( + 'idp storage path', + 'Path to data storage accessible by the IdP.', + '/var/lib/ipsilon/saml2'), + pconfig.String( + 'idp metadata file', + 'The IdP Metadata file genearated at install time.', + 'metadata.xml'), + pconfig.String( + 'idp certificate file', + 'The IdP PEM Certificate genearated at install time.', + 'certificate.pem'), + pconfig.String( + 'idp key file', + 'The IdP Certificate Key genearated at install time.', + 'certificate.key'), + pconfig.Condition( + 'allow self registration', + 'Allow authenticated users to register applications.', + True), + pconfig.Choice( + 'default allowed nameids', + 'Default Allowed NameIDs for Service Providers.', + metadata.SAML2_NAMEID_MAP.keys(), + ['persistent', 'transient', 'email', 'kerberos', 'x509']), + pconfig.Pick( + 'default nameid', + 'Default NameID used by Service Providers.', + metadata.SAML2_NAMEID_MAP.keys(), + 'persistent'), + pconfig.String( + 'default email domain', + 'Used for users missing the email property.', + 'example.com'), + pconfig.Condition( + 'enabled', + 'Whether the SAML IDP is enabled', + False) + ) if cherrypy.config.get('debug', False): import logging import sys diff --git a/ipsilon/util/config.py b/ipsilon/util/config.py index 304c630..94443e3 100755 --- a/ipsilon/util/config.py +++ b/ipsilon/util/config.py @@ -165,7 +165,7 @@ class List(Option): def export_value(self): if self._assigned_value: - return ', '.join(self._assigned_value) + return ','.join(self._assigned_value) return None def import_value(self, value): @@ -205,19 +205,16 @@ class Choice(Option): def set_value(self, value): if type(value) is not list: value = [value] + self._assigned_value = list() for val in value: - if type(val) is not tuple: - val = (val, True) - if val[0] not in self._allowed_values: + if val not in self._allowed_values: raise ValueError( - 'Value "%s" not allowed [%s]' % (val[0], + 'Value "%s" not allowed [%s]' % (val, self._allowed_values)) - if val[1] is True: - if val[0] not in self._assigned_value: - self._assigned_value.append(val[0]) - else: - if val[0] in self._assigned_value: - self._assigned_value.remove(val[0]) + self._assigned_value.append(val) + + if not self._assigned_value: + self._assigned_value = None def unset_value(self, value): if type(value) is str: @@ -236,11 +233,14 @@ class Choice(Option): def import_value(self, value): enabled = [x.strip() for x in value.split(',')] + if enabled: + if self._assigned_value is None: + self._assigned_value = list() for val in enabled: if val not in self._allowed_values: # We silently ignore invalid options on import for now continue - self._assigned_value[val] = True + self._assigned_value.append(val) class Pick(Option): @@ -268,30 +268,11 @@ class Pick(Option): self._str_import_value(value) -class Condition(Option): +class Condition(Pick): def __init__(self, name, description, default_value=False): - super(Condition, self).__init__(name, description) - self._default_value = default_value - - def set_value(self, value): - if value is True: - self._assigned_value = True - elif value is False: - self._assigned_value = False - else: - raise ValueError('Value must be True or False, got %s' % value) - - def export_value(self): - if self._assigned_value is True: - return '1' - elif self._assigned_value is False: - return '0' - else: - return None + super(Condition, self).__init__(name, description, + [True, False], default_value) def import_value(self, value): - if value in ['1', 'YES']: - self._assigned_value = True - else: - self._assigned_value = False + self._assigned_value = value == 'True' diff --git a/ipsilon/util/plugin.py b/ipsilon/util/plugin.py index 919a28d..f303078 100755 --- a/ipsilon/util/plugin.py +++ b/ipsilon/util/plugin.py @@ -21,6 +21,7 @@ import os import imp import cherrypy import inspect +from ipsilon.util.config import Config from ipsilon.util.data import AdminStore from ipsilon.util.log import Log @@ -112,82 +113,24 @@ class PluginObject(Log): def __init__(self): self.name = None self._config = None - self._options = None self._data = AdminStore() - def get_config_desc(self, name=None): - """ The configuration description is a dictionary that provides - A description of the supported configuration options, as well - as the default configuration option values. - The key is the option name, the value is an array of 3 elements: - - description - - option type - - default value - """ - if name is None: - return self._options - - opt = self._options.get(name, None) - if opt is None: - return '' - return opt[0] - - def _value_to_list(self, name): - if name not in self._config: - return - value = self._config[name] - if type(value) is list: - return - vlist = [x.strip() for x in value.split(',')] - self._config[name] = vlist - - def set_config(self, config): + def import_config(self, config): self._config = config - if self._config is None: - return - if self._options: - for name, opt in self._options.iteritems(): - if opt[1] == 'list': - self._value_to_list(name) - - def get_config_value(self, name): - value = None - if self._config: - value = self._config.get(name, None) - if value is None: - if self._options: - opt = self._options.get(name, None) - if opt: - value = opt[2] - if cherrypy.config.get('debug', False): - cherrypy.log('[%s] %s: %s' % (self.name, name, value)) - - return value - - def set_config_value(self, option, value): - if not self._config: - self._config = dict() - self._config[option] = value - if self._options and option in self._options: - if self._options[option][1] == 'list': - self._value_to_list(option) + def export_config(self): + return self._config def get_plugin_config(self, facility): return self._data.load_options(facility, self.name) def refresh_plugin_config(self, facility): config = self.get_plugin_config(facility) - self.set_config(config) + self.import_config(config) def save_plugin_config(self, facility, config=None): if config is None: - config = self._config - config = config.copy() - - for key, value in config.items(): - if type(value) is list: - config[key] = ','.join(value) + config = self.export_config() self._data.save_options(facility, self.name, config) @@ -209,3 +152,41 @@ class PluginObject(Log): def wipe_data(self): self._data.wipe_data(self.name) + + +class PluginConfig(Log): + + def __init__(self): + self._config = None + + def new_config(self, name, *config_args): + self._config = Config(name, *config_args) + + def get_config_obj(self): + if self._config is None: + raise AttributeError('Config not initialized') + return self._config + + def import_config(self, config): + if not self._config: + raise AttributeError('Config not initialized, cannot import') + + for key, value in config.iteritems(): + if key in self._config: + self._config[key].import_value(str(value)) + + def export_config(self): + config = dict() + for name, option in self._config.iteritems(): + config[name] = option.export_value() + return config + + def get_config_value(self, name): + if not self._config: + raise AttributeError('Config not initialized') + return self._config[name].get_value() + + def set_config_value(self, name, value): + if not self._config: + raise AttributeError('Config not initialized') + return self._config[name].set_value(value) diff --git a/templates/admin/plugin_config.html b/templates/admin/plugin_config.html index 1372f55..7071c7e 100644 --- a/templates/admin/plugin_config.html +++ b/templates/admin/plugin_config.html @@ -19,18 +19,59 @@ <div id="options"> <form class="form-horizontal" role="form" id="{{ name }}" action="{{ action }}" method="post" enctype="application/x-www-form-urlencoded"> - - {% for o in options_order %} + {% for k, v in config.iteritems() %} <div class="form-group"> - <label class="col-sm-2" for="{{ o }}">{{ o }}:</label> + <label class="col-sm-2" for="{{ v.name }}">{{ v.name }}:</label> <div class="col-sm-10"> - {% set val = plugin.get_config_value(o) %} - {% if val is string %} - <input type="text" class="form-control" name="{{ o }}" value="{{ val }}"> - {% else %} - <input type="text" class="form-control" name="{{ o }}" value="{{ val|join(', ') }}"> - {% endif %} - <span class="help-block">{{ plugin.get_config_desc(o) }}</span> + {%- set value = v.get_value() -%} + {% if v.__class__.__name__ in ['String', 'Template'] -%} + <input type="text" class="form-control" name="{{ v.name }}" + {%- if value %} + value="{{ value }}" + {%- endif -%} + > + {% elif v.__class__.__name__ == 'List' -%} + <textarea class="form-control" name="{{ v.name }}"> + {%- if value %} + {{- value|join('\n') -}} + {%- endif -%} + </textarea> + {% elif v.__class__.__name__ == 'Choice' -%} + {% set entries = v.get_allowed() -%} + <div class="row"> + {% for e in entries -%} + <div class="col-md-4"> + <input type="checkbox" name="{{ v.name }}_{{ e }}" + {%- if value and e in value %} + checked="true" + {%- endif -%} + > {{ e }} + </div> + {% endfor %} + </div> + {% elif v.__class__.__name__ == 'Pick' -%} + {% set entries = v.get_allowed() -%} + <div class="row"> + {% for e in entries -%} + <div class="col-md-4"> + <input type="radio" name="{{ v.name }}" value="{{ e }}" + {%- if e == value %} + checked="true" + {%- endif -%} + > {{ e }} + </div> + {% endfor %} + </div> + {% elif v.__class__.__name__ == 'Condition' -%} + <input type="checkbox" name="{{ v.name }}" + {%- if value %} + checked="true" + {% endif -%} + > + {% else -%} + {{ v.__class__.__name__ }} + {% endif -%} + <span class="help-block">{{ v.description }}</span> </div> </div> <hr> diff --git a/templates/admin/plugins.html b/templates/admin/plugins.html index 7bbe544..7ef50a0 100644 --- a/templates/admin/plugins.html +++ b/templates/admin/plugins.html @@ -28,7 +28,7 @@ <a class="text-info" href="{{ baseurl }}/disable/{{ p }}">Disable</a> </div> <div class="col-md-6 col-sm-6 col-xs-12"> - {%- if available[p].get_config_desc() %} + {%- if available[p].get_config_obj() %} <a class="text-primary" href="{{ baseurl }}/{{ p }}">Configure</a> {% endif %} </div> @@ -88,7 +88,7 @@ <a class="text-info" href="{{ baseurl }}/enable/{{ p }}">Enable</a> </div> <div class="col-md-6 col-sm-6 col-xs-12"> - {%- if available[p].get_config_desc() %} + {%- if available[p].get_config_obj() %} <span class="text-muted">Configure</span> {% endif %} </div> diff --git a/templates/admin/providers.html b/templates/admin/providers.html index c0147d8..333b10d 100644 --- a/templates/admin/providers.html +++ b/templates/admin/providers.html @@ -28,7 +28,7 @@ <p class="text-info"><a href="{{ baseurl }}/disable/{{ p }}">Disable</a></p> </div> <div class="col-md-4 col-sm-4 col-xs-12"> - {%- if available[p].get_config_desc() %} + {%- if available[p].get_config_obj() %} <p class="text-primary"><a href="{{ baseurl }}/{{ p }}">Configure</a></p> {% endif %} </div> @@ -54,7 +54,7 @@ <p class="text-info"><a href="{{ baseurl }}/enable/{{ p }}">Enable</a></p> </div> <div class="col-md-4 col-sm-4 col-xs-12"> - {%- if available[p].get_config_desc() %} + {%- if available[p].get_config_obj() %} <p class="text-muted">Configure</p> {% endif %} </div> |