summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <simo@redhat.com>2014-10-23 11:45:32 -0400
committerPatrick Uiterwijk <puiterwijk@redhat.com>2014-11-12 23:47:15 +0100
commit83da2bf3963db3e4427bced3b4c0681e751e54da (patch)
tree53f03ce8e60d2c68453cdb5fe6be9aad7ce2c362
parent0c14f7600de70baf5b3ee609288207dcdb65e1ae (diff)
downloadipsilon.git-83da2bf3963db3e4427bced3b4c0681e751e54da.tar.gz
ipsilon.git-83da2bf3963db3e4427bced3b4c0681e751e54da.tar.xz
ipsilon.git-83da2bf3963db3e4427bced3b4c0681e751e54da.zip
Refactor plugin configuration
Fork a PluginConfig class out of PluginObject, the base object now supports a simple dictionary config, while using PluginConfig provide access to structured util.config based configuration. Change UI code that deal with plugins configuration to properly use the new structured config objects in order to represent data in appropriate format based on the data type. Use the new util.config objects to represent plugins configuration. Signed-off-by: Simo Sorce <simo@redhat.com> Reviewed-by: Patrick Uiterwijk <puiterwijk@redhat.com>
-rwxr-xr-xipsilon/admin/common.py74
-rwxr-xr-xipsilon/info/common.py11
-rwxr-xr-xipsilon/info/infoldap.py55
-rwxr-xr-xipsilon/info/nss.py1
-rwxr-xr-xipsilon/login/authfas.py63
-rwxr-xr-xipsilon/login/authform.py34
-rwxr-xr-xipsilon/login/authkrb.py1
-rwxr-xr-xipsilon/login/authldap.py74
-rwxr-xr-xipsilon/login/authpam.py44
-rwxr-xr-xipsilon/login/authtest.py36
-rwxr-xr-xipsilon/login/common.py11
-rwxr-xr-xipsilon/providers/common.py17
-rwxr-xr-xipsilon/providers/openid/auth.py4
-rwxr-xr-xipsilon/providers/openid/extensions/common.py10
-rwxr-xr-xipsilon/providers/openid/meta.py4
-rwxr-xr-xipsilon/providers/openidp.py69
-rwxr-xr-xipsilon/providers/saml2idp.py84
-rwxr-xr-xipsilon/util/config.py51
-rwxr-xr-xipsilon/util/plugin.py107
-rw-r--r--templates/admin/plugin_config.html61
-rw-r--r--templates/admin/plugins.html4
-rw-r--r--templates/admin/providers.html4
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 -%}
+ >&nbsp;{{ 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 -%}
+ >&nbsp;{{ 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>