mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Giant webui patch take 2
This commit is contained in:
@@ -1,4 +1,2 @@
|
|||||||
include LICENSE TODO lite-webui.py lite-xmlrpc.py
|
include LICENSE TODO lite-server.py
|
||||||
graft tests/
|
include tests/*/*.py
|
||||||
graft ipawebui/static/
|
|
||||||
include ipawebui/templates/*.kid
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ BuildRequires: popt-devel
|
|||||||
BuildRequires: /usr/share/selinux/devel/Makefile
|
BuildRequires: /usr/share/selinux/devel/Makefile
|
||||||
BuildRequires: m4
|
BuildRequires: m4
|
||||||
BuildRequires: policycoreutils >= %{POLICYCOREUTILSVER}
|
BuildRequires: policycoreutils >= %{POLICYCOREUTILSVER}
|
||||||
BuildRequires: python-cherrypy
|
|
||||||
BuildRequires: python-setuptools
|
BuildRequires: python-setuptools
|
||||||
BuildRequires: python-krbV
|
BuildRequires: python-krbV
|
||||||
BuildRequires: xmlrpc-c-devel
|
BuildRequires: xmlrpc-c-devel
|
||||||
@@ -75,7 +74,8 @@ Requires: mod_nss
|
|||||||
%endif
|
%endif
|
||||||
Requires: python-ldap
|
Requires: python-ldap
|
||||||
Requires: python-krbV
|
Requires: python-krbV
|
||||||
Requires: python-cherrypy
|
Requires: python-assets
|
||||||
|
Requires: python-wehjit
|
||||||
Requires: acl
|
Requires: acl
|
||||||
Requires: python-pyasn1
|
Requires: python-pyasn1
|
||||||
Requires: libcap
|
Requires: libcap
|
||||||
@@ -440,6 +440,10 @@ fi
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Oct 12 2009 Jason Gerard DeRose <jderose@redhat.com> - 1.99-8
|
||||||
|
- Removed python-cherrypy from BuildRequires and Requires
|
||||||
|
- Added Requires python-assets, python-wehjit
|
||||||
|
|
||||||
* Mon Aug 24 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-7
|
* Mon Aug 24 2009 Rob Crittenden <rcritten@redhat.com> - 1.99-7
|
||||||
- Added httpd SELinux policy so CRLs can be read
|
- Added httpd SELinux policy so CRLs can be read
|
||||||
|
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ cli_plugins = (
|
|||||||
def run(api):
|
def run(api):
|
||||||
error = None
|
error = None
|
||||||
try:
|
try:
|
||||||
argv = api.bootstrap_with_global_options(context='cli')
|
(options, argv) = api.bootstrap_with_global_options(context='cli')
|
||||||
for klass in cli_plugins:
|
for klass in cli_plugins:
|
||||||
api.register(klass)
|
api.register(klass)
|
||||||
api.load_plugins()
|
api.load_plugins()
|
||||||
|
|||||||
@@ -99,11 +99,23 @@ DEFAULT_CONFIG = (
|
|||||||
('container_virtual', 'cn=virtual operations'),
|
('container_virtual', 'cn=virtual operations'),
|
||||||
|
|
||||||
# Ports, hosts, and URIs:
|
# Ports, hosts, and URIs:
|
||||||
('lite_xmlrpc_port', 8888),
|
# FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
|
||||||
('lite_webui_port', 9999),
|
('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
|
||||||
('xmlrpc_uri', 'http://localhost:8888'),
|
('rpc_json_uri', 'http://localhost:8888/ipa/json'),
|
||||||
('ldap_uri', 'ldap://localhost:389'),
|
('ldap_uri', 'ldap://localhost:389'),
|
||||||
|
|
||||||
|
# Web Application mount points
|
||||||
|
('mount_ipa', '/ipa/'),
|
||||||
|
('mount_xmlserver', 'xml'),
|
||||||
|
('mount_jsonserver', 'json'),
|
||||||
|
('mount_webui', 'ui/'),
|
||||||
|
('mount_webui_assets', '_/'),
|
||||||
|
|
||||||
|
# WebUI stuff:
|
||||||
|
('webui_prod', True),
|
||||||
|
('webui_assets_dir', None),
|
||||||
|
('webui_assets_dburi', None),
|
||||||
|
|
||||||
# Debugging:
|
# Debugging:
|
||||||
('verbose', False),
|
('verbose', False),
|
||||||
('debug', False),
|
('debug', False),
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ class PrivateError(StandardError):
|
|||||||
|
|
||||||
def __init__(self, **kw):
|
def __init__(self, **kw):
|
||||||
self.msg = self.format % kw
|
self.msg = self.format % kw
|
||||||
|
self.kw = kw
|
||||||
for (key, value) in kw.iteritems():
|
for (key, value) in kw.iteritems():
|
||||||
assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % (
|
assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % (
|
||||||
self.__class__.__name__, key, value,
|
self.__class__.__name__, key, value,
|
||||||
@@ -244,6 +245,7 @@ class PublicError(StandardError):
|
|||||||
format = None
|
format = None
|
||||||
|
|
||||||
def __init__(self, format=None, message=None, **kw):
|
def __init__(self, format=None, message=None, **kw):
|
||||||
|
self.kw = kw
|
||||||
name = self.__class__.__name__
|
name = self.__class__.__name__
|
||||||
if self.format is not None and format is not None:
|
if self.format is not None and format is not None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -407,6 +409,15 @@ class ServerNetworkError(PublicError):
|
|||||||
format = _('error on server %(server)r: %(error)s')
|
format = _('error on server %(server)r: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
|
class JSONError(PublicError):
|
||||||
|
"""
|
||||||
|
**909** Raised when server recieved a malformed JSON-RPC request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
errno = 909
|
||||||
|
format = _('Invalid JSON-RPC request: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# 1000 - 1999: Authentication errors
|
# 1000 - 1999: Authentication errors
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ class Command(HasParam):
|
|||||||
options = None
|
options = None
|
||||||
params = None
|
params = None
|
||||||
output_for_cli = None
|
output_for_cli = None
|
||||||
|
obj = None
|
||||||
|
|
||||||
def __call__(self, *args, **options):
|
def __call__(self, *args, **options):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -132,6 +132,13 @@ class DefaultFrom(ReadOnly):
|
|||||||
)
|
)
|
||||||
lock(self)
|
lock(self)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
args = (self.callback.__name__,) + tuple(repr(k) for k in self.keys)
|
||||||
|
return '%s(%s)' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
', '.join(args)
|
||||||
|
)
|
||||||
|
|
||||||
def __call__(self, **kw):
|
def __call__(self, **kw):
|
||||||
"""
|
"""
|
||||||
Call the callback if all keys are present.
|
Call the callback if all keys are present.
|
||||||
@@ -376,7 +383,12 @@ class Param(ReadOnly):
|
|||||||
for rule in self.rules:
|
for rule in self.rules:
|
||||||
yield rule.__name__
|
yield rule.__name__
|
||||||
for key in sorted(self.__kw):
|
for key in sorted(self.__kw):
|
||||||
yield '%s=%r' % (key, self.__kw[key])
|
value = self.__kw[key]
|
||||||
|
if callable(value) and hasattr(value, '__name__'):
|
||||||
|
value = value.__name__
|
||||||
|
else:
|
||||||
|
value = repr(value)
|
||||||
|
yield '%s=%s' % (key, value)
|
||||||
|
|
||||||
def __call__(self, value, **kw):
|
def __call__(self, value, **kw):
|
||||||
"""
|
"""
|
||||||
@@ -389,6 +401,16 @@ class Param(ReadOnly):
|
|||||||
self.validate(value)
|
self.validate(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def kw(self):
|
||||||
|
"""
|
||||||
|
Iterate through ``(key,value)`` for all kwargs passed to constructor.
|
||||||
|
"""
|
||||||
|
for key in sorted(self.__kw):
|
||||||
|
value = self.__kw[key]
|
||||||
|
if callable(value) and hasattr(value, '__name__'):
|
||||||
|
value = value.__name__
|
||||||
|
yield (key, value)
|
||||||
|
|
||||||
def use_in_context(self, env):
|
def use_in_context(self, env):
|
||||||
"""
|
"""
|
||||||
Return ``True`` if this parameter should be used in ``env.context``.
|
Return ``True`` if this parameter should be used in ``env.context``.
|
||||||
@@ -770,6 +792,27 @@ class Bool(Param):
|
|||||||
type = bool
|
type = bool
|
||||||
type_error = _('must be True or False')
|
type_error = _('must be True or False')
|
||||||
|
|
||||||
|
# FIXME: This my quick hack to get some UI stuff working, change these defaults
|
||||||
|
# --jderose 2009-08-28
|
||||||
|
kwargs = Param.kwargs + (
|
||||||
|
('truths', frozenset, frozenset([1, u'1', u'True'])),
|
||||||
|
('falsehoods', frozenset, frozenset([0, u'0', u'False'])),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _convert_scalar(self, value, index=None):
|
||||||
|
"""
|
||||||
|
Convert a single scalar value.
|
||||||
|
"""
|
||||||
|
if type(value) is self.type:
|
||||||
|
return value
|
||||||
|
if value in self.truths:
|
||||||
|
return True
|
||||||
|
if value in self.falsehoods:
|
||||||
|
return False
|
||||||
|
raise ConversionError(name=self.name, index=index,
|
||||||
|
error=ugettext(self.type_error),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Flag(Bool):
|
class Flag(Bool):
|
||||||
"""
|
"""
|
||||||
@@ -1220,7 +1263,7 @@ class GeneralizedTime(Str):
|
|||||||
mm = int(t[2:4])
|
mm = int(t[2:4])
|
||||||
if mm < 0 or mm > 59:
|
if mm < 0 or mm > 59:
|
||||||
raise ValueError('MM out of range')
|
raise ValueError('MM out of range')
|
||||||
|
|
||||||
def _check_dotw(self, t):
|
def _check_dotw(self, t):
|
||||||
if t.isnumeric():
|
if t.isnumeric():
|
||||||
value = int(t)
|
value = int(t)
|
||||||
@@ -1266,7 +1309,7 @@ class GeneralizedTime(Str):
|
|||||||
raise ValueError('month number non-numeric')
|
raise ValueError('month number non-numeric')
|
||||||
value = int(t)
|
value = int(t)
|
||||||
if value < 1 or value > 12:
|
if value < 1 or value > 12:
|
||||||
raise ValueError('month number out of range')
|
raise ValueError('month number out of range')
|
||||||
|
|
||||||
def _check_interval(self, t, check_func):
|
def _check_interval(self, t, check_func):
|
||||||
intervals = t.split(',')
|
intervals = t.split(',')
|
||||||
@@ -1364,7 +1407,7 @@ class GeneralizedTime(Str):
|
|||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
name=self.cli_name, errors='incomplete time value'
|
name=self.cli_name, errors='incomplete time value'
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def create_param(spec):
|
def create_param(spec):
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ class API(DictProxy):
|
|||||||
if context is not None:
|
if context is not None:
|
||||||
overrides['context'] = context
|
overrides['context'] = context
|
||||||
self.bootstrap(**overrides)
|
self.bootstrap(**overrides)
|
||||||
return args
|
return (options, args)
|
||||||
|
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -28,12 +28,7 @@ from ipalib import api
|
|||||||
from ipalib.backend import Backend
|
from ipalib.backend import Backend
|
||||||
import krbV
|
import krbV
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Is it safe to assume the Kerberos library is using UTF-8 for the
|
|
||||||
# principal and realm? If not, how do we query the Kerberos library to find
|
|
||||||
# the encoding it's using?
|
|
||||||
ENCODING = 'UTF-8'
|
ENCODING = 'UTF-8'
|
||||||
FS_ENCODING = (sys.getfilesystemencoding() or sys.getdefaultencoding())
|
|
||||||
|
|
||||||
|
|
||||||
class krb(Backend):
|
class krb(Backend):
|
||||||
@@ -61,7 +56,7 @@ class krb(Backend):
|
|||||||
"""
|
"""
|
||||||
Return the ``krbV.CCache`` for the ``ccname`` credential ccache.
|
Return the ``krbV.CCache`` for the ``ccname`` credential ccache.
|
||||||
"""
|
"""
|
||||||
return krbV.CCache(ccname.encode(FS_ENCODING))
|
return krbV.CCache(ccname)
|
||||||
|
|
||||||
def __get_principal(self, ccname):
|
def __get_principal(self, ccname):
|
||||||
"""
|
"""
|
||||||
@@ -78,7 +73,7 @@ class krb(Backend):
|
|||||||
This cannot return anything meaningful if used in the server as a
|
This cannot return anything meaningful if used in the server as a
|
||||||
request is processed.
|
request is processed.
|
||||||
"""
|
"""
|
||||||
return self.__default_ccache().name.decode(FS_ENCODING)
|
return self.__default_ccache().name
|
||||||
|
|
||||||
def default_principal(self):
|
def default_principal(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -45,20 +45,21 @@ class env(LocalOrRemote):
|
|||||||
keys.add(key)
|
keys.add(key)
|
||||||
elif query in self.env:
|
elif query in self.env:
|
||||||
keys.add(query)
|
keys.add(query)
|
||||||
return sorted(keys)
|
return keys
|
||||||
|
|
||||||
def execute(self, variables, **options):
|
def execute(self, variables, **options):
|
||||||
if variables is None:
|
if variables is None:
|
||||||
keys = self.env
|
keys = self.env
|
||||||
else:
|
else:
|
||||||
keys = self.__find_keys(variables)
|
keys = self.__find_keys(variables)
|
||||||
return tuple(
|
return dict(
|
||||||
(key, self.env[key]) for key in keys
|
(key, self.env[key]) for key in keys
|
||||||
)
|
)
|
||||||
|
|
||||||
def output_for_cli(self, textui, result, variables, **options):
|
def output_for_cli(self, textui, result, variables, **options):
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
return
|
return
|
||||||
|
result = tuple((k, result[k]) for k in sorted(result))
|
||||||
if len(result) == 1:
|
if len(result) == 1:
|
||||||
textui.print_keyval(result)
|
textui.print_keyval(result)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -70,15 +70,15 @@ class user(LDAPObject):
|
|||||||
takes_params = (
|
takes_params = (
|
||||||
Str('givenname',
|
Str('givenname',
|
||||||
cli_name='first',
|
cli_name='first',
|
||||||
doc='first name',
|
doc='First name',
|
||||||
),
|
),
|
||||||
Str('sn',
|
Str('sn',
|
||||||
cli_name='last',
|
cli_name='last',
|
||||||
doc='last name',
|
doc='Last name',
|
||||||
),
|
),
|
||||||
Str('uid',
|
Str('uid',
|
||||||
cli_name='user',
|
cli_name='user',
|
||||||
doc='login name',
|
doc='Login name',
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
default_from=lambda givenname, sn: givenname[0] + sn,
|
default_from=lambda givenname, sn: givenname[0] + sn,
|
||||||
normalizer=lambda value: value.lower(),
|
normalizer=lambda value: value.lower(),
|
||||||
@@ -90,7 +90,7 @@ class user(LDAPObject):
|
|||||||
),
|
),
|
||||||
Str('homedirectory?',
|
Str('homedirectory?',
|
||||||
cli_name='homedir',
|
cli_name='homedir',
|
||||||
doc='home directory',
|
doc='Home directory',
|
||||||
default_from=lambda uid: '/home/%s' % uid,
|
default_from=lambda uid: '/home/%s' % uid,
|
||||||
),
|
),
|
||||||
Str('loginshell?',
|
Str('loginshell?',
|
||||||
@@ -251,4 +251,3 @@ class user_unlock(LDAPQuery):
|
|||||||
textui.print_dashed('Unlocked user "%s".' % keys[-1])
|
textui.print_dashed('Unlocked user "%s".' % keys[-1])
|
||||||
|
|
||||||
api.register(user_unlock)
|
api.register(user_unlock)
|
||||||
|
|
||||||
|
|||||||
@@ -25,5 +25,6 @@ XML-RPC client plugin.
|
|||||||
from ipalib import api
|
from ipalib import api
|
||||||
|
|
||||||
if 'in_server' in api.env and api.env.in_server is True:
|
if 'in_server' in api.env and api.env.in_server is True:
|
||||||
from ipaserver.rpcserver import xmlserver
|
from ipaserver.rpcserver import xmlserver, jsonserver
|
||||||
api.register(xmlserver)
|
api.register(xmlserver)
|
||||||
|
api.register(jsonserver)
|
||||||
|
|||||||
@@ -23,11 +23,25 @@ RPC server.
|
|||||||
Also see the `ipalib.rpc` module.
|
Also see the `ipalib.rpc` module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from urlparse import parse_qs
|
||||||
from xmlrpclib import Fault
|
from xmlrpclib import Fault
|
||||||
from ipalib.backend import Executioner
|
from ipalib.backend import Executioner
|
||||||
from ipalib.errors import PublicError, InternalError, CommandError
|
from ipalib.errors import PublicError, InternalError, CommandError, JSONError
|
||||||
|
from ipalib.request import context, Connection, destroy_context
|
||||||
from ipalib.rpc import xml_dumps, xml_loads
|
from ipalib.rpc import xml_dumps, xml_loads
|
||||||
from ipalib.util import make_repr
|
from ipalib.util import make_repr
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def read_input(environ):
|
||||||
|
"""
|
||||||
|
Read the request body from environ['wsgi.input'].
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
length = int(environ.get('CONTENT_LENGTH'))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return
|
||||||
|
return environ['wsgi.input'].read(length)
|
||||||
|
|
||||||
|
|
||||||
def params_2_args_options(params):
|
def params_2_args_options(params):
|
||||||
@@ -39,13 +53,116 @@ def params_2_args_options(params):
|
|||||||
return (params, dict())
|
return (params, dict())
|
||||||
|
|
||||||
|
|
||||||
class xmlserver(Executioner):
|
def nicify_query(query, encoding='utf-8'):
|
||||||
|
if not query:
|
||||||
|
return
|
||||||
|
for (key, value) in query.iteritems():
|
||||||
|
if len(value) == 0:
|
||||||
|
yield (key, None)
|
||||||
|
elif len(value) == 1:
|
||||||
|
yield (key, value[0].decode(encoding))
|
||||||
|
else:
|
||||||
|
yield (key, tuple(v.decode(encoding) for v in value))
|
||||||
|
|
||||||
|
|
||||||
|
def extract_query(environ):
|
||||||
|
"""
|
||||||
|
Return the query as a ``dict``, or ``None`` if no query is presest.
|
||||||
|
"""
|
||||||
|
qstr = None
|
||||||
|
if environ['REQUEST_METHOD'] == 'POST':
|
||||||
|
if environ['CONTENT_TYPE'] == 'application/x-www-form-urlencoded':
|
||||||
|
qstr = read_input(environ)
|
||||||
|
elif environ['REQUEST_METHOD'] == 'GET':
|
||||||
|
qstr = environ['QUERY_STRING']
|
||||||
|
if qstr:
|
||||||
|
query = dict(nicify_query(
|
||||||
|
parse_qs(qstr, keep_blank_values=True)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
query = {}
|
||||||
|
environ['wsgi.query'] = query
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
class WSGIExecutioner(Executioner):
|
||||||
|
"""
|
||||||
|
Base class for execution backends with a WSGI application interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
url = self.env['mount_' + self.name]
|
||||||
|
if url.startswith('/'):
|
||||||
|
self.url = url
|
||||||
|
else:
|
||||||
|
self.url = self.env.mount_ipa + url
|
||||||
|
super(WSGIExecutioner, self).finalize()
|
||||||
|
|
||||||
|
def execute(self, environ):
|
||||||
|
result = None
|
||||||
|
error = None
|
||||||
|
_id = None
|
||||||
|
try:
|
||||||
|
self.create_context(ccache=environ.get('KRB5CCNAME'))
|
||||||
|
if (
|
||||||
|
environ.get('CONTENT_TYPE', '').startswith(self.content_type)
|
||||||
|
and environ['REQUEST_METHOD'] == 'POST'
|
||||||
|
):
|
||||||
|
data = read_input(environ)
|
||||||
|
(name, args, options, _id) = self.unmarshal(data)
|
||||||
|
else:
|
||||||
|
(name, args, options, _id) = self.simple_unmarshal(environ)
|
||||||
|
if name not in self.Command:
|
||||||
|
raise CommandError(name=name)
|
||||||
|
result = self.Command[name](*args, **options)
|
||||||
|
except PublicError, e:
|
||||||
|
error = e
|
||||||
|
except StandardError, e:
|
||||||
|
self.exception(
|
||||||
|
'non-public: %s: %s', e.__class__.__name__, str(e)
|
||||||
|
)
|
||||||
|
error = InternalError()
|
||||||
|
finally:
|
||||||
|
destroy_context()
|
||||||
|
return self.marshal(result, error, _id)
|
||||||
|
|
||||||
|
def simple_unmarshal(self, environ):
|
||||||
|
name = environ['PATH_INFO'].strip('/')
|
||||||
|
options = extract_query(environ)
|
||||||
|
return (name, tuple(), options, None)
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
"""
|
||||||
|
WSGI application for execution.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
status = '200 OK'
|
||||||
|
response = self.execute(environ)
|
||||||
|
headers = [('Content-Type', self.content_type + '; charset=utf-8')]
|
||||||
|
except StandardError, e:
|
||||||
|
self.exception('%s.__call__():', self.name)
|
||||||
|
status = '500 Internal Server Error'
|
||||||
|
response = status
|
||||||
|
headers = [('Content-Type', 'text/plain')]
|
||||||
|
start_response(status, headers)
|
||||||
|
return [response]
|
||||||
|
|
||||||
|
def unmarshal(self, data):
|
||||||
|
raise NotImplementedError('%s.unmarshal()' % self.fullname)
|
||||||
|
|
||||||
|
def marshal(self, result, error, _id=None):
|
||||||
|
raise NotImplementedError('%s.marshal()' % self.fullname)
|
||||||
|
|
||||||
|
|
||||||
|
class xmlserver(WSGIExecutioner):
|
||||||
"""
|
"""
|
||||||
Execution backend plugin for XML-RPC server.
|
Execution backend plugin for XML-RPC server.
|
||||||
|
|
||||||
Also see the `ipalib.rpc.xmlclient` plugin.
|
Also see the `ipalib.rpc.xmlclient` plugin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
content_type = 'text/xml'
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
self.__system = {
|
self.__system = {
|
||||||
'system.listMethods': self.listMethods,
|
'system.listMethods': self.listMethods,
|
||||||
@@ -79,3 +196,75 @@ class xmlserver(Executioner):
|
|||||||
self.info('response: %s: %s', e.__class__.__name__, str(e))
|
self.info('response: %s: %s', e.__class__.__name__, str(e))
|
||||||
response = Fault(e.errno, e.strerror)
|
response = Fault(e.errno, e.strerror)
|
||||||
return xml_dumps(response, methodresponse=True)
|
return xml_dumps(response, methodresponse=True)
|
||||||
|
|
||||||
|
def unmarshal(self, data):
|
||||||
|
(params, name) = xml_loads(data)
|
||||||
|
(args, options) = params_2_args_options(params)
|
||||||
|
return (name, args, options, None)
|
||||||
|
|
||||||
|
def marshal(self, result, error, _id=None):
|
||||||
|
if error:
|
||||||
|
response = Fault(error.errno, error.strerror)
|
||||||
|
else:
|
||||||
|
response = (result,)
|
||||||
|
return xml_dumps(response, methodresponse=True)
|
||||||
|
|
||||||
|
|
||||||
|
class jsonserver(WSGIExecutioner):
|
||||||
|
"""
|
||||||
|
JSON RPC server.
|
||||||
|
|
||||||
|
For information on the JSON-RPC spec, see:
|
||||||
|
|
||||||
|
http://json-rpc.org/wiki/specification
|
||||||
|
"""
|
||||||
|
|
||||||
|
content_type = 'application/json'
|
||||||
|
|
||||||
|
def marshal(self, result, error, _id=None):
|
||||||
|
if error:
|
||||||
|
assert isinstance(error, PublicError)
|
||||||
|
error = dict(
|
||||||
|
code=error.errno,
|
||||||
|
message=error.strerror,
|
||||||
|
name=error.__class__.__name__,
|
||||||
|
kw=dict(error.kw),
|
||||||
|
)
|
||||||
|
response = dict(
|
||||||
|
result=result,
|
||||||
|
error=error,
|
||||||
|
id=_id,
|
||||||
|
)
|
||||||
|
return json.dumps(response, sort_keys=True, indent=4)
|
||||||
|
|
||||||
|
def unmarshal(self, data):
|
||||||
|
try:
|
||||||
|
d = json.loads(data)
|
||||||
|
except ValueError, e:
|
||||||
|
raise JSONError(error=e)
|
||||||
|
if not isinstance(d, dict):
|
||||||
|
raise JSONError(error='Request must be a dict')
|
||||||
|
if 'method' not in d:
|
||||||
|
raise JSONError(error='Request is missing "method"')
|
||||||
|
if 'params' not in d:
|
||||||
|
raise JSONError(error='Request is missing "params"')
|
||||||
|
method = d['method']
|
||||||
|
params = d['params']
|
||||||
|
_id = d.get('id')
|
||||||
|
if not isinstance(params, (list, tuple)):
|
||||||
|
raise JSONError(error='params must be a list')
|
||||||
|
if len(params) != 2:
|
||||||
|
raise JSONError(
|
||||||
|
error='params must contain [args, options]'
|
||||||
|
)
|
||||||
|
args = params[0]
|
||||||
|
if not isinstance(args, (list, tuple)):
|
||||||
|
raise JSONError(
|
||||||
|
error='params[0] (aka args) must be a list'
|
||||||
|
)
|
||||||
|
options = params[1]
|
||||||
|
if not isinstance(options, dict):
|
||||||
|
raise JSONError(
|
||||||
|
error='params[1] (aka options) must be a dict'
|
||||||
|
)
|
||||||
|
return (method, args, options, _id)
|
||||||
|
|||||||
@@ -17,8 +17,40 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Package containing web-based UI components.
|
IPA web UI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import kid
|
from controllers import JSON
|
||||||
kid.enable_import()
|
from engine import Engine
|
||||||
|
from widgets import create_widgets
|
||||||
|
|
||||||
|
from assetslib import Assets
|
||||||
|
from wehjit import Application
|
||||||
|
|
||||||
|
|
||||||
|
def join_url(base, url):
|
||||||
|
if url.startswith('/'):
|
||||||
|
return url
|
||||||
|
return base + url
|
||||||
|
|
||||||
|
|
||||||
|
def create_wsgi_app(api):
|
||||||
|
baseurl = api.env.mount_ipa
|
||||||
|
assets = Assets(
|
||||||
|
url=join_url(baseurl, api.env.mount_webui_assets),
|
||||||
|
dir=api.env.webui_assets_dir,
|
||||||
|
prod=api.env.webui_prod,
|
||||||
|
)
|
||||||
|
app = Application(
|
||||||
|
url=join_url(baseurl, api.env.mount_webui),
|
||||||
|
assets=assets,
|
||||||
|
widgets=create_widgets(),
|
||||||
|
prod=api.env.webui_prod,
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = Engine(api, app)
|
||||||
|
engine.build()
|
||||||
|
|
||||||
|
app.finalize()
|
||||||
|
|
||||||
|
return app
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
# Authors: Jason Gerard DeRose <jderose@redhat.com>
|
|
||||||
#
|
|
||||||
# Copyright (C) 2008 Red Hat
|
|
||||||
# see file 'COPYING' for use and warranty information
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License as
|
|
||||||
# published by the Free Software Foundation; version 2 only
|
|
||||||
#
|
|
||||||
# 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 General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
"""
|
|
||||||
Controller classes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import simplejson
|
|
||||||
from ipalib.plugable import ReadOnly, lock
|
|
||||||
|
|
||||||
|
|
||||||
class Controller(ReadOnly):
|
|
||||||
exposed = True
|
|
||||||
|
|
||||||
def __init__(self, template=None):
|
|
||||||
self.template = template
|
|
||||||
lock(self)
|
|
||||||
|
|
||||||
def output_xhtml(self, **kw):
|
|
||||||
return self.template.serialize(
|
|
||||||
output='xhtml-strict',
|
|
||||||
format='pretty',
|
|
||||||
**kw
|
|
||||||
)
|
|
||||||
|
|
||||||
def output_json(self, **kw):
|
|
||||||
return simplejson.dumps(kw, sort_keys=True, indent=4)
|
|
||||||
|
|
||||||
def __call__(self, **kw):
|
|
||||||
json = bool(kw.pop('_format', None) == 'json')
|
|
||||||
result = self.run(**kw)
|
|
||||||
assert type(result) is dict
|
|
||||||
if json or self.template is None:
|
|
||||||
return self.output_json(**result)
|
|
||||||
return self.output_xhtml(**result)
|
|
||||||
|
|
||||||
def run(self, **kw):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class Command(Controller):
|
|
||||||
def __init__(self, command, template=None):
|
|
||||||
self.command = command
|
|
||||||
super(Command, self).__init__(template)
|
|
||||||
|
|
||||||
def run(self, **kw):
|
|
||||||
return dict(command=self.command)
|
|
||||||
|
|
||||||
|
|
||||||
class Index(Controller):
|
|
||||||
def __init__(self, api, template=None):
|
|
||||||
self.api = api
|
|
||||||
super(Index, self).__init__(template)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
return dict(api=self.api)
|
|
||||||
59
ipawebui/controllers.py
Normal file
59
ipawebui/controllers.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Authors: Jason Gerard DeRose <jderose@redhat.com>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Red Hat
|
||||||
|
# see file 'COPYING' for use and warranty information
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License as
|
||||||
|
# published by the Free Software Foundation; version 2 only
|
||||||
|
#
|
||||||
|
# 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
"""
|
||||||
|
Controllers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wehjit import util
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class JSON(object):
|
||||||
|
def __init__(self, url, api):
|
||||||
|
self.url = url
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '%s(url=%r)' % (self.__class__.__name__, self.url)
|
||||||
|
|
||||||
|
def __call__(self, env, start):
|
||||||
|
util.extract_query(env)
|
||||||
|
start('200 OK', [('Content-Type', 'text/plain')])
|
||||||
|
for key in sorted(env):
|
||||||
|
yield '%s = %r\n' % (key, env[key])
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
def __init__(self, url, cmd, api):
|
||||||
|
self.url = url
|
||||||
|
self.cmd = cmd
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '%s(url=%r)' % (self.__class__.__name__, self.url)
|
||||||
|
|
||||||
|
def __call__(self, env, start):
|
||||||
|
kw = util.extract_query(env)
|
||||||
|
ccname = env['KRB5CCNAME']
|
||||||
|
self.api.Backend.xmlserver.create_context(ccname)
|
||||||
|
result = self.api.Backend.xmlserver.execute(self.cmd.name, **kw)
|
||||||
|
start('200 OK', [('Content-Type', 'text/plain')])
|
||||||
|
return [
|
||||||
|
json.dumps(result, sort_keys=True, indent=4)
|
||||||
|
]
|
||||||
152
ipawebui/engine.py
Normal file
152
ipawebui/engine.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Authors: Jason Gerard DeRose <jderose@redhat.com>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Red Hat
|
||||||
|
# see file 'COPYING' for use and warranty information
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License as
|
||||||
|
# published by the Free Software Foundation; version 2 only
|
||||||
|
#
|
||||||
|
# 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
"""
|
||||||
|
Engine to map ipalib plugins to wehjit widgets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from controllers import Command
|
||||||
|
|
||||||
|
class ParamMapper(object):
|
||||||
|
def __init__(self, api, app):
|
||||||
|
self._api = api
|
||||||
|
self._app = app
|
||||||
|
self.__methods = dict()
|
||||||
|
for name in dir(self):
|
||||||
|
if name.startswith('_') or name.endswith('_'):
|
||||||
|
continue
|
||||||
|
attr = getattr(self, name)
|
||||||
|
if not callable(attr):
|
||||||
|
continue
|
||||||
|
self.__methods[name] = attr
|
||||||
|
|
||||||
|
def __call__(self, param, cmd):
|
||||||
|
key = param.__class__.__name__
|
||||||
|
if key in self.__methods:
|
||||||
|
method = self.__methods[key]
|
||||||
|
else:
|
||||||
|
#raise Warning('No ParamMapper for %r' % key)
|
||||||
|
method = self.Str
|
||||||
|
return method(param, cmd)
|
||||||
|
|
||||||
|
def Str(self, param, cmd):
|
||||||
|
return self._app.new('TextRow',
|
||||||
|
label=param.cli_name,
|
||||||
|
name=param.name,
|
||||||
|
required=param.required,
|
||||||
|
value=param.default,
|
||||||
|
)
|
||||||
|
|
||||||
|
def Password(self, param, cmd):
|
||||||
|
return self._app.new('PasswordRow',
|
||||||
|
name=param.name,
|
||||||
|
required=param.required,
|
||||||
|
)
|
||||||
|
|
||||||
|
def Flag(self, param, cmd):
|
||||||
|
return self._app.new('SelectRow',
|
||||||
|
name=param.name,
|
||||||
|
label=param.cli_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Engine(object):
|
||||||
|
def __init__(self, api, app):
|
||||||
|
self.api = api
|
||||||
|
self.app = app
|
||||||
|
self.param_mapper = ParamMapper(api, app)
|
||||||
|
self.pages = dict()
|
||||||
|
self.jsonurl = self.api.Backend.jsonserver.url.rstrip('/')
|
||||||
|
self.info_pages = []
|
||||||
|
|
||||||
|
def add_object_menuitems(self, menu, name):
|
||||||
|
obj = self.api.Object[name]
|
||||||
|
for cmd in obj.methods():
|
||||||
|
p = self.pages[cmd.name]
|
||||||
|
menu.add(
|
||||||
|
menu.new('MenuItem',
|
||||||
|
label=p.title,
|
||||||
|
href=p.url,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
for cmd in self.api.Command():
|
||||||
|
self.pages[cmd.name] = self.build_page(cmd)
|
||||||
|
for page in self.pages.itervalues():
|
||||||
|
page.menu.label = 'Users'
|
||||||
|
self.add_object_menuitems(page.menu, 'user')
|
||||||
|
|
||||||
|
menu = page.new('Menu', label='Groups')
|
||||||
|
page.menuset.add(menu)
|
||||||
|
self.add_object_menuitems(menu, 'group')
|
||||||
|
|
||||||
|
# Add in the info pages:
|
||||||
|
page = self.app.new('PageApp', id='api', title='api')
|
||||||
|
page.view.add(
|
||||||
|
self.app.new('API', api=self.api)
|
||||||
|
)
|
||||||
|
self.info_pages.append(page)
|
||||||
|
|
||||||
|
for kind in self.api:
|
||||||
|
self.build_info_page(kind)
|
||||||
|
for page in self.info_pages:
|
||||||
|
for p in self.info_pages:
|
||||||
|
page.menuset.add(
|
||||||
|
self.app.new('MenuItem',
|
||||||
|
href=p.url,
|
||||||
|
label=p.title,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_info_page(self, kind):
|
||||||
|
# Add in the Object page:
|
||||||
|
plugins = tuple(self.api[kind]())
|
||||||
|
page = self.app.new('PageApp', id=kind, title=kind)
|
||||||
|
info = self.app.new('IPAPlugins', kind=kind, plugins=plugins)
|
||||||
|
quick_jump = self.app.new('QuickJump',
|
||||||
|
options=tuple((p.name, p.name) for p in plugins)
|
||||||
|
)
|
||||||
|
page.view.add(info)
|
||||||
|
page.actions.add(quick_jump)
|
||||||
|
self.info_pages.append(page)
|
||||||
|
if kind in self.app.widgets:
|
||||||
|
info.add(
|
||||||
|
self.app.new(kind)
|
||||||
|
)
|
||||||
|
return page
|
||||||
|
|
||||||
|
def build_page(self, cmd):
|
||||||
|
page = self.app.new('PageApp',
|
||||||
|
id=cmd.name,
|
||||||
|
title=cmd.summary.rstrip('.'),
|
||||||
|
)
|
||||||
|
#page.form.action = self.app.url + '__json__'
|
||||||
|
page.actions.add(
|
||||||
|
self.app.new('Submit')
|
||||||
|
)
|
||||||
|
table = self.app.new('FieldTable')
|
||||||
|
page.view.add(table)
|
||||||
|
for param in cmd.params():
|
||||||
|
field = self.param_mapper(param, cmd)
|
||||||
|
table.add(field)
|
||||||
|
|
||||||
|
page.form.action = '/'.join([self.jsonurl, cmd.name])
|
||||||
|
|
||||||
|
|
||||||
|
return page
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Authors:
|
|
||||||
# Jason Gerard DeRose <jderose@redhat.com>
|
|
||||||
#
|
|
||||||
# Copyright (C) 2008 Red Hat
|
|
||||||
# see file 'COPYING' for use and warranty information
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License as
|
|
||||||
# published by the Free Software Foundation; version 2 only
|
|
||||||
#
|
|
||||||
# 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 General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
"""
|
|
||||||
Production Web UI using mod_python.
|
|
||||||
"""
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
|||||||
# Authors: Jason Gerard DeRose <jderose@redhat.com>
|
|
||||||
#
|
|
||||||
# Copyright (C) 2008 Red Hat
|
|
||||||
# see file 'COPYING' for use and warranty information
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License as
|
|
||||||
# published by the Free Software Foundation; version 2 only
|
|
||||||
#
|
|
||||||
# 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 General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
"""
|
|
||||||
Sub-package containing Kid templates.
|
|
||||||
"""
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<html xmlns:py="http://purl.org/kid/ns#">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Hello</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<table>
|
|
||||||
<tr py:for="param in command.params()">
|
|
||||||
<td py:content="param.name"/>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<html xmlns:py="http://purl.org/kid/ns#">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>FreeIPA</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<p py:for="name in api.Command">
|
|
||||||
<a href="${name}" py:content="name"/>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
228
ipawebui/widgets.py
Normal file
228
ipawebui/widgets.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# Authors: Jason Gerard DeRose <jderose@redhat.com>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Red Hat
|
||||||
|
# see file 'COPYING' for use and warranty information
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License as
|
||||||
|
# published by the Free Software Foundation; version 2 only
|
||||||
|
#
|
||||||
|
# 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
"""
|
||||||
|
Custom IPA widgets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
|
from wehjit import Collection, base, freeze
|
||||||
|
from wehjit.util import Alternator
|
||||||
|
from wehjit import Static, Dynamic, StaticProp, DynamicProp
|
||||||
|
|
||||||
|
|
||||||
|
class IPAPlugins(base.Container):
|
||||||
|
plugins = Static('plugins', default=tuple())
|
||||||
|
kind = Static('kind')
|
||||||
|
|
||||||
|
@DynamicProp
|
||||||
|
def row(self):
|
||||||
|
return Alternator(['odd', 'even'])
|
||||||
|
|
||||||
|
xml = """
|
||||||
|
<div
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
class="${css_classes}"
|
||||||
|
id="${id}"
|
||||||
|
>
|
||||||
|
<p py:content="'%d %s plugins' % (len(plugins), kind)" />
|
||||||
|
|
||||||
|
<div py:for="p in plugins">
|
||||||
|
<h2 id="${p.name}"><a href="#${p.name}" py:content="p.name" /></h2>
|
||||||
|
|
||||||
|
<table class="${row.reset()}">
|
||||||
|
|
||||||
|
<tr class="${row.next()}">
|
||||||
|
<td>module</td>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
title="Link to module documentation"
|
||||||
|
href="http://freeipa.org/developer-docs/${p.module}-module.html"
|
||||||
|
py:content="p.module"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr py:if="p.doc" class="${row.next()}">
|
||||||
|
<td>docstring</td>
|
||||||
|
<td><pre py:content="p.doc" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr
|
||||||
|
py:for="child in children"
|
||||||
|
py:replace="child.generate(plugin=p, row=row)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
style_global = (
|
||||||
|
('tr.odd', (
|
||||||
|
('background-color', '#ddd'),
|
||||||
|
)),
|
||||||
|
('tr.even', (
|
||||||
|
('background-color', '#eee'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('td', (
|
||||||
|
('vertical-align', 'top'),
|
||||||
|
('padding', '0.25em 0.5em'),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
style = (
|
||||||
|
('', (
|
||||||
|
('font-size', '%(font_size_mono)s'),
|
||||||
|
('font-family', 'monospace'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('table', (
|
||||||
|
('width', '100%%'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('pre', (
|
||||||
|
('margin', '0'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('th', (
|
||||||
|
('color', '#0a0'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('h2', (
|
||||||
|
('font-family', 'monospace'),
|
||||||
|
('font-weight', 'normal'),
|
||||||
|
('margin-top', '1.5em'),
|
||||||
|
('margin-bottom', '0'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('h2 a', (
|
||||||
|
('text-decoration', 'none'),
|
||||||
|
('color', 'inherit'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('h2 a:hover', (
|
||||||
|
('background-color', '#eee'),
|
||||||
|
)),
|
||||||
|
|
||||||
|
('h2:target', (
|
||||||
|
('color', '#e02'),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class API(base.Widget):
|
||||||
|
api = Static('api')
|
||||||
|
|
||||||
|
@DynamicProp
|
||||||
|
def row(self):
|
||||||
|
return Alternator(['odd', 'even'])
|
||||||
|
|
||||||
|
xml = """
|
||||||
|
<div
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
class="${css_classes}"
|
||||||
|
id="${id}"
|
||||||
|
>
|
||||||
|
<p py:content="'%d namespaces in API' % len(api)" />
|
||||||
|
<table>
|
||||||
|
<tr py:for="key in api" class="${row.next()}">
|
||||||
|
<td>
|
||||||
|
<a href="${key}" py:content="'api.' + key" />
|
||||||
|
</td>
|
||||||
|
<td py:content="repr(api[key])" />
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Command(base.Widget):
|
||||||
|
xml = """
|
||||||
|
<table
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
py:strip="True"
|
||||||
|
>
|
||||||
|
|
||||||
|
<tr py:if="plugin.obj" class="${row.next()}">
|
||||||
|
<td>Object</td>
|
||||||
|
<td>
|
||||||
|
<a href="Object#${plugin.obj.name}" py:content="plugin.obj.fullname" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr py:if="plugin.args" class="${row.next()}">
|
||||||
|
<th colspan="2" py:content="'args (%d)' % len(plugin.args)" />
|
||||||
|
</tr>
|
||||||
|
<tr py:for="arg in plugin.args()" class="${row.next()}">
|
||||||
|
<td py:content="arg.name"/>
|
||||||
|
<td py:content="repr(arg)" />
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr py:if="plugin.options" class="${row.next()}">
|
||||||
|
<th colspan="2" py:content="'options (%d)' % len(plugin.options)" />
|
||||||
|
</tr>
|
||||||
|
<tr py:for="option in plugin.options()" class="${row.next()}">
|
||||||
|
<td py:content="option.name"/>
|
||||||
|
<td py:content="repr(option)" />
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Object(base.Widget):
|
||||||
|
xml = """
|
||||||
|
<table
|
||||||
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
py:strip="True"
|
||||||
|
>
|
||||||
|
<tr py:if="plugin.methods" class="${row.next()}">
|
||||||
|
<th colspan="2" py:content="'methods (%d)' % len(plugin.methods)" />
|
||||||
|
</tr>
|
||||||
|
<tr py:for="method in plugin.methods()" class="${row.next()}">
|
||||||
|
<td><a href="${'Command#' + method.name}" py:content="method.name"/></td>
|
||||||
|
<td py:content="method.summary" />
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr py:if="plugin.params" class="${row.next()}">
|
||||||
|
<th colspan="2" py:content="'params (%d)' % len(plugin.params)" />
|
||||||
|
</tr>
|
||||||
|
<tr py:for="param in plugin.params()" class="${row.next()}">
|
||||||
|
<td py:content="param.name"/>
|
||||||
|
<td py:content="repr(param)" />
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def create_widgets():
|
||||||
|
widgets = Collection('freeIPA')
|
||||||
|
widgets.register_builtins()
|
||||||
|
|
||||||
|
widgets.register(API)
|
||||||
|
widgets.register(IPAPlugins)
|
||||||
|
widgets.register(Command)
|
||||||
|
widgets.register(Object)
|
||||||
|
|
||||||
|
|
||||||
|
freeze(widgets)
|
||||||
|
return widgets
|
||||||
108
lite-server.py
Executable file
108
lite-server.py
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Authors:
|
||||||
|
# Jason Gerard DeRose <jderose@redhat.com>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Red Hat
|
||||||
|
# see file 'COPYING' for use and warranty information
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License as
|
||||||
|
# published by the Free Software Foundation; version 2 only
|
||||||
|
#
|
||||||
|
# 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
"""
|
||||||
|
In-tree paste-based test server.
|
||||||
|
|
||||||
|
This uses the *Python Paste* WSGI server. For more info, see:
|
||||||
|
|
||||||
|
http://pythonpaste.org/
|
||||||
|
|
||||||
|
Unfortunately, SSL support is broken under Python 2.6 with paste 1.7.2, see:
|
||||||
|
|
||||||
|
http://trac.pythonpaste.org/pythonpaste/ticket/314
|
||||||
|
"""
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
import optparse
|
||||||
|
from paste import httpserver
|
||||||
|
import paste.gzipper
|
||||||
|
from paste.urlmap import URLMap
|
||||||
|
from assetslib.wsgi import AssetsApp
|
||||||
|
from ipalib import api
|
||||||
|
import ipawebui
|
||||||
|
|
||||||
|
|
||||||
|
class KRBCheater(object):
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.url = app.url
|
||||||
|
self.ccname = api.Backend.krb.default_ccname()
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['KRB5CCNAME'] = self.ccname
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
|
||||||
|
parser.add_option('--dev',
|
||||||
|
help='Run WebUI in development mode (requires FireBug)',
|
||||||
|
default=True,
|
||||||
|
action='store_false',
|
||||||
|
dest='prod',
|
||||||
|
)
|
||||||
|
parser.add_option('--host',
|
||||||
|
help='Listen on address HOST (default 127.0.0.1)',
|
||||||
|
default='127.0.0.1',
|
||||||
|
)
|
||||||
|
parser.add_option('--port',
|
||||||
|
help='Listen on PORT (default 8888)',
|
||||||
|
default=8888,
|
||||||
|
type='int',
|
||||||
|
)
|
||||||
|
|
||||||
|
api.env.in_server = True
|
||||||
|
(options, args) = api.bootstrap_with_global_options(parser, context='lite')
|
||||||
|
api.env._merge(
|
||||||
|
lite_port=options.port,
|
||||||
|
lite_host=options.host,
|
||||||
|
webui_prod=options.prod,
|
||||||
|
lite_pem=api.env._join('dot_ipa', 'lite.pem'),
|
||||||
|
)
|
||||||
|
api.finalize()
|
||||||
|
|
||||||
|
ui = ipawebui.create_wsgi_app(api)
|
||||||
|
ui.render_assets()
|
||||||
|
|
||||||
|
urlmap = URLMap()
|
||||||
|
apps = [
|
||||||
|
('XML RPC', api.Backend.xmlserver),
|
||||||
|
('JSON RPC', api.Backend.jsonserver),
|
||||||
|
('Assets', AssetsApp(ui.assets)),
|
||||||
|
('Web UI', ui),
|
||||||
|
]
|
||||||
|
for (name, app) in apps:
|
||||||
|
urlmap[app.url] = KRBCheater(app)
|
||||||
|
api.log.info('Mounting %s at %s', name, app.url)
|
||||||
|
|
||||||
|
if path.isfile(api.env.lite_pem):
|
||||||
|
pem = api.env.lite_pem
|
||||||
|
else:
|
||||||
|
api.log.info('To enable SSL, place PEM file at %r', api.env.lite_pem)
|
||||||
|
pem = None
|
||||||
|
|
||||||
|
httpserver.serve(paste.gzipper.middleware(urlmap),
|
||||||
|
host=api.env.lite_host,
|
||||||
|
port=api.env.lite_port,
|
||||||
|
ssl_pem=pem,
|
||||||
|
)
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Authors:
|
|
||||||
# Jason Gerard DeRose <jderose@redhat.com>
|
|
||||||
#
|
|
||||||
# Copyright (C) 2008 Red Hat
|
|
||||||
# see file 'COPYING' for use and warranty information
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License as
|
|
||||||
# published by the Free Software Foundation; version 2 only
|
|
||||||
#
|
|
||||||
# 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 General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
"""
|
|
||||||
In-tree Web UI using cherrypy.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from cherrypy import expose, config, quickstart
|
|
||||||
from ipawebui.templates import form, main
|
|
||||||
from ipawebui import controller
|
|
||||||
from ipalib import api
|
|
||||||
|
|
||||||
api.load_plugins()
|
|
||||||
api.finalize()
|
|
||||||
|
|
||||||
|
|
||||||
class root(object):
|
|
||||||
index = controller.Index(api, main)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
for cmd in api.Command():
|
|
||||||
ctr = controller.Command(cmd, form)
|
|
||||||
setattr(self, cmd.name, ctr)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
quickstart(root())
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Authors:
|
|
||||||
# Jason Gerard DeRose <jderose@redhat.com>
|
|
||||||
#
|
|
||||||
# Copyright (C) 2008 Red Hat
|
|
||||||
# see file 'COPYING' for use and warranty information
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License as
|
|
||||||
# published by the Free Software Foundation; version 2 only
|
|
||||||
#
|
|
||||||
# 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 General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
"""
|
|
||||||
In-tree XML-RPC server using SimpleXMLRPCServer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
|
|
||||||
import krbV
|
|
||||||
from ipalib import api
|
|
||||||
|
|
||||||
|
|
||||||
class Server(SimpleXMLRPCServer):
|
|
||||||
"""
|
|
||||||
Custom server implementing `Server._marshaled_dispatch()`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _marshaled_dispatch(self, data, dispatch_method=None):
|
|
||||||
"""
|
|
||||||
Use `ipaserver.rpcserver.xmlserver.marshaled_dispatch()`.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
ccache=krbV.default_context().default_ccache().name
|
|
||||||
return api.Backend.xmlserver.marshaled_dispatch(data, ccache)
|
|
||||||
except Exception, e:
|
|
||||||
api.log.exception('lite-xmlrpc: caught error in _marshaled_dispatch()')
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
class RequestHandler(SimpleXMLRPCRequestHandler):
|
|
||||||
|
|
||||||
def do_POST(self):
|
|
||||||
try:
|
|
||||||
client = '%r %r' % self.client_address
|
|
||||||
except Exception, e:
|
|
||||||
api.log.exception('lite-xmlrpc: caught error in do_POST()')
|
|
||||||
raise e
|
|
||||||
return SimpleXMLRPCRequestHandler.do_POST(self)
|
|
||||||
|
|
||||||
|
|
||||||
api.bootstrap_with_global_options(context='server')
|
|
||||||
api.finalize()
|
|
||||||
|
|
||||||
kw = dict(requestHandler=RequestHandler, logRequests=False)
|
|
||||||
if sys.version_info[:2] != (2, 4):
|
|
||||||
kw.update(dict(encoding='UTF-8', allow_none=True))
|
|
||||||
server = Server(('', api.env.lite_xmlrpc_port), **kw)
|
|
||||||
|
|
||||||
api.log.info('Logging to file %r', api.env.log)
|
|
||||||
api.log.info('Listening on port %d', api.env.lite_xmlrpc_port)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
server.serve_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
api.log.info('KeyboardInterrupt: shutting down server...')
|
|
||||||
server.server_close()
|
|
||||||
api.log.info('Server shutdown.')
|
|
||||||
7
setup.py
7
setup.py
@@ -23,7 +23,7 @@
|
|||||||
Python-level packaging using distutils.
|
Python-level packaging using distutils.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from setuptools import setup
|
from distutils.core import setup
|
||||||
import ipalib
|
import ipalib
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
@@ -38,11 +38,6 @@ setup(
|
|||||||
'ipaserver.plugins',
|
'ipaserver.plugins',
|
||||||
'ipaserver.install',
|
'ipaserver.install',
|
||||||
'ipawebui',
|
'ipawebui',
|
||||||
'ipawebui.templates',
|
|
||||||
],
|
],
|
||||||
package_data={
|
|
||||||
'ipawebui.templates': ['*.kid'],
|
|
||||||
'ipawebui': ['static/*'],
|
|
||||||
},
|
|
||||||
scripts=['ipa'],
|
scripts=['ipa'],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -60,6 +60,27 @@ class test_DefaultFrom(ClassChecker):
|
|||||||
e = raises(TypeError, self.cls, callback, 'givenname', 17)
|
e = raises(TypeError, self.cls, callback, 'givenname', 17)
|
||||||
assert str(e) == TYPE_ERROR % ('keys', str, 17, int)
|
assert str(e) == TYPE_ERROR % ('keys', str, 17, int)
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
"""
|
||||||
|
Test the `ipalib.parameters.DefaultFrom.__repr__` method.
|
||||||
|
"""
|
||||||
|
def stuff(one, two):
|
||||||
|
pass
|
||||||
|
|
||||||
|
o = self.cls(stuff)
|
||||||
|
assert repr(o) == "DefaultFrom(stuff, 'one', 'two')"
|
||||||
|
|
||||||
|
o = self.cls(stuff, 'aye', 'bee', 'see')
|
||||||
|
assert repr(o) == "DefaultFrom(stuff, 'aye', 'bee', 'see')"
|
||||||
|
|
||||||
|
cb = lambda first, last: first[0] + last
|
||||||
|
|
||||||
|
o = self.cls(cb)
|
||||||
|
assert repr(o) == "DefaultFrom(<lambda>, 'first', 'last')"
|
||||||
|
|
||||||
|
o = self.cls(cb, 'aye', 'bee', 'see')
|
||||||
|
assert repr(o) == "DefaultFrom(<lambda>, 'aye', 'bee', 'see')"
|
||||||
|
|
||||||
def test_call(self):
|
def test_call(self):
|
||||||
"""
|
"""
|
||||||
Test the `ipalib.parameters.DefaultFrom.__call__` method.
|
Test the `ipalib.parameters.DefaultFrom.__call__` method.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from tests.util import create_test_api, raises, PluginTester
|
|||||||
from tests.data import unicode_str
|
from tests.data import unicode_str
|
||||||
from ipalib import errors, Command
|
from ipalib import errors, Command
|
||||||
from ipaserver import rpcserver
|
from ipaserver import rpcserver
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
def test_params_2_args_options():
|
def test_params_2_args_options():
|
||||||
@@ -50,3 +51,65 @@ class test_xmlserver(PluginTester):
|
|||||||
|
|
||||||
def test_marshaled_dispatch(self):
|
def test_marshaled_dispatch(self):
|
||||||
(o, api, home) = self.instance('Backend', in_server=True)
|
(o, api, home) = self.instance('Backend', in_server=True)
|
||||||
|
|
||||||
|
|
||||||
|
class test_jsonserver(PluginTester):
|
||||||
|
"""
|
||||||
|
Test the `ipaserver.rpcserver.jsonserver` plugin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_plugin = rpcserver.jsonserver
|
||||||
|
|
||||||
|
def test_unmarshal(self):
|
||||||
|
"""
|
||||||
|
Test the `ipaserver.rpcserver.jsonserver.unmarshal` method.
|
||||||
|
"""
|
||||||
|
(o, api, home) = self.instance('Backend', in_server=True)
|
||||||
|
|
||||||
|
# Test with invalid JSON-data:
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, 'this wont work')
|
||||||
|
assert isinstance(e.error, ValueError)
|
||||||
|
assert str(e.error) == 'No JSON object could be decoded'
|
||||||
|
|
||||||
|
# Test with non-dict type:
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps([1, 2, 3]))
|
||||||
|
assert str(e.error) == 'Request must be a dict'
|
||||||
|
|
||||||
|
params = [[1, 2], dict(three=3, four=4)]
|
||||||
|
# Test with missing method:
|
||||||
|
d = dict(params=params, id=18)
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps(d))
|
||||||
|
assert str(e.error) == 'Request is missing "method"'
|
||||||
|
|
||||||
|
# Test with missing params:
|
||||||
|
d = dict(method='echo', id=18)
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps(d))
|
||||||
|
assert str(e.error) == 'Request is missing "params"'
|
||||||
|
|
||||||
|
# Test with non-list params:
|
||||||
|
for p in ('hello', dict(args=tuple(), options=dict())):
|
||||||
|
d = dict(method='echo', id=18, params=p)
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps(d))
|
||||||
|
assert str(e.error) == 'params must be a list'
|
||||||
|
|
||||||
|
# Test with other than 2 params:
|
||||||
|
for p in ([], [tuple()], [None, dict(), tuple()]):
|
||||||
|
d = dict(method='echo', id=18, params=p)
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps(d))
|
||||||
|
assert str(e.error) == 'params must contain [args, options]'
|
||||||
|
|
||||||
|
# Test when args is not a list:
|
||||||
|
d = dict(method='echo', id=18, params=['args', dict()])
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps(d))
|
||||||
|
assert str(e.error) == 'params[0] (aka args) must be a list'
|
||||||
|
|
||||||
|
# Test when options is not a dict:
|
||||||
|
d = dict(method='echo', id=18, params=[('hello', 'world'), 'options'])
|
||||||
|
e = raises(errors.JSONError, o.unmarshal, json.dumps(d))
|
||||||
|
assert str(e.error) == 'params[1] (aka options) must be a dict'
|
||||||
|
|
||||||
|
# Test with valid values:
|
||||||
|
args = [u'jdoe']
|
||||||
|
options = dict(givenname=u'John', sn='Doe')
|
||||||
|
d = dict(method=u'user_add', params=[args, options], id=18)
|
||||||
|
assert o.unmarshal(json.dumps(d)) == (u'user_add', args, options, 18)
|
||||||
|
|||||||
@@ -17,54 +17,8 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Test the `ipawebui.controller` module.
|
Test the `ipawebui.controllers` module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ipawebui import controller
|
from wsgiref import util
|
||||||
|
from wsgiref.validate import validator
|
||||||
|
|
||||||
|
|
||||||
class test_Controller(object):
|
|
||||||
"""
|
|
||||||
Test the `controller.Controller` class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_init(self):
|
|
||||||
"""
|
|
||||||
Test the `ipawebui.controller.Controller.__init__()` method.
|
|
||||||
"""
|
|
||||||
o = controller.Controller()
|
|
||||||
assert o.template is None
|
|
||||||
template = 'The template.'
|
|
||||||
o = controller.Controller(template)
|
|
||||||
assert o.template is template
|
|
||||||
|
|
||||||
def test_output_xhtml(self):
|
|
||||||
"""
|
|
||||||
Test the `ipawebui.controller.Controller.output_xhtml` method.
|
|
||||||
"""
|
|
||||||
class Template(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.calls = 0
|
|
||||||
self.kw = {}
|
|
||||||
|
|
||||||
def serialize(self, **kw):
|
|
||||||
self.calls += 1
|
|
||||||
self.kw = kw
|
|
||||||
return dict(kw)
|
|
||||||
|
|
||||||
d = dict(output='xhtml-strict', format='pretty')
|
|
||||||
t = Template()
|
|
||||||
o = controller.Controller(t)
|
|
||||||
assert o.output_xhtml() == d
|
|
||||||
assert t.calls == 1
|
|
||||||
|
|
||||||
def test_output_json(self):
|
|
||||||
"""
|
|
||||||
Test the `ipawebui.controller.Controller.output_json` method.
|
|
||||||
"""
|
|
||||||
o = controller.Controller()
|
|
||||||
assert o.output_json() == '{}'
|
|
||||||
e = '{\n "age": 27, \n "first": "John", \n "last": "Doe"\n}'
|
|
||||||
j = o.output_json(last='Doe', first='John', age=27)
|
|
||||||
assert j == e
|
|
||||||
|
|||||||
Reference in New Issue
Block a user