Merge branch 'develop' into hotfixes-2.3.1

This commit is contained in:
Benjamin Dauvergne 2010-09-03 19:07:08 +02:00
commit 3f336a8e83
18 changed files with 233 additions and 70 deletions

View File

@ -364,7 +364,7 @@ protected static native void destroy(long cptr);
elif c[0] == 's':
print >>fd, wrapper_decl(s,'jstring')
print >>fd, ') {'
print >>fd, ' return (*env)->NewStringUTF(env, %s);' % c[1]
print >>fd, ' return (*env)->NewStringUTF(env, (char*) %s);' % c[1]
print >>fd, '}'
elif c[0] == 'b':
print >>fd, wrapper_decl(s,'jboolean')

View File

@ -223,7 +223,7 @@ INCLUDE: LassoNode.xs
if type == 'i':
self.xs.pn('ct = newSViv(%s);' % name)
elif type == 's':
self.xs.pn('ct = newSVpv(%s, 0);' % name)
self.xs.pn('ct = newSVpv((char*)%s, 0);' % name)
elif type == 'b': # only one case LASSO_WSF_ENABLED
self.xs.unindent()
self.xs.pn('''#ifdef %s

View File

@ -76,7 +76,7 @@ PHP_MINIT_FUNCTION(lasso)
if c[0] == 'i':
print >> self.fd, ' REGISTER_LONG_CONSTANT("%s", %s, CONST_CS|CONST_PERSISTENT);' % (c[1], c[1])
elif c[0] == 's':
print >> self.fd, ' REGISTER_STRING_CONSTANT("%s", %s, CONST_CS|CONST_PERSISTENT);' % (c[1], c[1])
print >> self.fd, ' REGISTER_STRING_CONSTANT("%s", (char*) %s, CONST_CS|CONST_PERSISTENT);' % (c[1], c[1])
elif c[0] == 'b':
print >> self.fd, '''\
#ifdef %s

View File

@ -692,7 +692,7 @@ register_constants(PyObject *d)
if c[0] == 'i':
print >> fd, ' obj = PyInt_FromLong(%s);' % c[1]
elif c[0] == 's':
print >> fd, ' obj = PyString_FromString(%s);' % c[1]
print >> fd, ' obj = PyString_FromString((char*)%s);' % c[1]
elif c[0] == 'b':
print >> fd, '''\
#ifdef %s

View File

@ -1,4 +1,4 @@
<Project
<Project
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns="http://usefulinc.com/ns/doap#"
@ -29,12 +29,12 @@
<implements>
<Specification rdf:about="http://www.projectliberty.org/liberty/content/download/1266/8160/file/liberty-idff-1.2-20050520.zip">
<rdfs:label>Liberty Alliance ID-FF 1.2</rdfs:label>
</Specification
</Specification>
</implements>
<implements>
<Specification rdf:about="http://docs.oasis-open.org/security/saml/v2.0/saml-2.0-os.zip">
<rdfs:label>OASIS SAML 2.0</rdfs:label>
</Specification
</Specification>
</implements>
<maintainer>
<foaf:Person>

View File

@ -1028,6 +1028,7 @@ _lasso_provider_load_metadata_from_doc(LassoProvider *provider, xmlDoc *doc)
g_return_val_if_fail(LASSO_IS_PROVIDER(provider), FALSE);
if (doc == NULL) {
warning("Metadata is not an XML document");
return FALSE;
}

View File

@ -46,6 +46,7 @@
#include "../xml/saml-2.0/samlp2_status_response.h"
#include "../xml/saml-2.0/samlp2_response.h"
#include "../xml/saml-2.0/saml2_assertion.h"
#include "../xml/misc_text_node.h"
#include "../utils.h"
#include "../debug.h"
@ -62,6 +63,7 @@ static gint lasso_profile_saml20_build_artifact_post_response_msg(LassoProfile *
const char *service);
static gboolean has_signature(LassoNode *node, LassoSignatureMethod *signature_method,
char **private_key_file, char **private_key_password);
static char* lasso_saml20_profile_generate_artifact(LassoProfile *profile, int part);
#define check_msg_body \
if (! profile->msg_body) { \
@ -152,20 +154,24 @@ http_method_to_binding(LassoHttpMethod method) {
*
* Return value: the generated artifact (internally allocated, don't free)
**/
char*
static char*
lasso_saml20_profile_generate_artifact(LassoProfile *profile, int part)
{
LassoNode *what = NULL;
lasso_assign_new_string(profile->private_data->artifact,
lasso_saml20_profile_build_artifact(&profile->server->parent));
if (part == 0) {
lasso_assign_new_string(profile->private_data->artifact_message,
lasso_node_dump(profile->request));
what = profile->request;
} else if (part == 1) {
lasso_assign_new_string(profile->private_data->artifact_message,
lasso_node_dump(profile->response));
what = profile->response;
} else {
/* XXX: RequestDenied here? */
}
/* Remove signature at the response level, if needed if will be on the ArtifactResponse */
lasso_node_remove_signature(what);
/* Keep an XML copy of the response for later retrieval */
lasso_assign_new_string(profile->private_data->artifact_message,
lasso_node_export_to_xml(what));
return profile->private_data->artifact;
}
@ -378,34 +384,43 @@ int
lasso_saml20_profile_build_artifact_response(LassoProfile *profile)
{
LassoSamlp2StatusResponse *response = NULL;
LassoNode *resp = NULL;
int rc = 0;
if ( ! LASSO_IS_SAMLP2_REQUEST_ABSTRACT(profile->request)) {
return LASSO_PROFILE_ERROR_MISSING_REQUEST;
}
/* Setup the response */
response = LASSO_SAMLP2_STATUS_RESPONSE(lasso_samlp2_artifact_response_new());
if (profile->private_data->artifact_message) {
resp = lasso_node_new_from_dump(profile->private_data->artifact_message);
lasso_assign_new_gobject(LASSO_SAMLP2_ARTIFACT_RESPONSE(response)->any, resp);
}
lasso_assign_new_gobject(profile->response, response);
response->ID = lasso_build_unique_id(32);
lasso_assign_string(response->Version, "2.0");
response->Issuer = LASSO_SAML2_NAME_ID(lasso_saml2_name_id_new_with_string(
LASSO_PROVIDER(profile->server)->ProviderID));
response->IssueInstant = lasso_get_current_time();
lasso_assign_string(response->InResponseTo, LASSO_SAMLP2_REQUEST_ABSTRACT(profile->request)->ID);
/* Add content */
if (profile->private_data->artifact_message) {
xmlDoc *doc;
xmlNode *node;
char *content = profile->private_data->artifact_message;
doc = lasso_xml_parse_memory(content, strlen(content));
if (doc) {
node = xmlDocGetRootElement(doc);
lasso_assign_new_gobject(LASSO_SAMLP2_ARTIFACT_RESPONSE(response)->any,
lasso_misc_text_node_new_with_xml_node(node));
lasso_release_doc(doc);
lasso_saml20_profile_set_response_status(profile,
LASSO_SAML2_STATUS_CODE_SUCCESS, NULL);
} else {
lasso_saml20_profile_set_response_status(profile,
LASSO_SAML2_STATUS_CODE_RESPONDER,
LASSO_PRIVATE_STATUS_CODE_FAILED_TO_RESTORE_ARTIFACT);
}
}
/* Setup the signature */
lasso_check_good_rc(lasso_profile_saml20_setup_message_signature(profile,
(LassoNode*)response));
lasso_assign_new_gobject(profile->response, LASSO_NODE(response));
if (resp == NULL) {
lasso_saml20_profile_set_response_status(profile,
LASSO_SAML2_STATUS_CODE_REQUESTER, NULL);
} else {
lasso_saml20_profile_set_response_status(profile,
LASSO_SAML2_STATUS_CODE_SUCCESS, NULL);
}
/* Serialize the message */
lasso_assign_new_string(profile->msg_body, lasso_node_export_to_soap(profile->response));
cleanup:
return rc;

View File

@ -40,7 +40,6 @@ extern "C" {
int lasso_saml20_profile_init_request(LassoProfile *profile, const char *remote_provider_id,
gboolean first_in_session, LassoSamlp2RequestAbstract *request_abstract,
LassoHttpMethod http_method, LassoMdProtocolType protocol_type);
char* lasso_saml20_profile_generate_artifact(LassoProfile *profile, int part);
#define lasso_saml20_profile_set_response_status_success(profile, code2) \
lasso_saml20_profile_set_response_status(profile, LASSO_SAML2_STATUS_CODE_SUCCESS, code2)
#define lasso_saml20_profile_set_response_status_responder(profile, code2) \

View File

@ -477,6 +477,12 @@ lasso_saml20_provider_load_metadata(LassoProvider *provider, xmlNode *root_node)
(! loaded_one_or_more_descriptor || (pdata->roles & provider->role) == 0)) {
/* We must at least load one descriptor, and we must load a descriptor for our
* assigned role or we fail. */
if (! loaded_one_or_more_descriptor) {
warning("No descriptor was loaded, failing");
}
if ((pdata->roles & provider->role) == 0) {
warning("Loaded roles and prescribed role does not intersect");
}
return FALSE;
}

View File

@ -107,6 +107,13 @@
*/
#define LASSO_SOAP_FAULT_CODE_VERSION_MISMATCH "s:VersionMismatch"
/**
* LASSO_PRIVATE_STATUS_CODE_FAILED_TO_RESTORE_ARTIFACT:
*
* An artifact content is present but Lasso failed to rebuild the corresponding XML content.
*/
#define LASSO_PRIVATE_STATUS_CODE_FAILED_TO_RESTORE_ARTIFACT "FailedToRestoreArtifact"
/*****************************************************************************/
/* Lasso */
/*****************************************************************************/
@ -131,6 +138,41 @@
*/
#define LASSO_PYTHON_HREF "http://www.entrouvert.org/namespaces/python/0.0"
/**
* LASSO_SIGNATURE_TYPE_ATTRIBUTE:
*
* Attribute name for the Lasso signature type attribute.
*/
#define LASSO_SIGNATURE_TYPE_ATTRIBUTE BAD_CAST "SignatureType"
/**
* LASSO_SIGNATURE_METHOD_ATTRIBUTE:
*
* Attribute name for the Lasso signature type attribute.
*/
#define LASSO_SIGNATURE_METHOD_ATTRIBUTE BAD_CAST "SignatureMethod"
/**
* LASSO_PRIVATE_KEY_ATTRIBUTE:
*
* Attribute name for the Lasso private key attribute.
*/
#define LASSO_PRIVATE_KEY_ATTRIBUTE BAD_CAST "PrivateKey"
/**
* LASSO_PRIVATE_KEY_PASSWORD_ATTRIBUTE:
*
* Attribute name for the Lasso private key attribute.
*/
#define LASSO_PRIVATE_KEY_PASSWORD_ATTRIBUTE BAD_CAST "PrivateKeyPassword"
/**
* LASSO_CERTIFICATE_ATTRIBUTE:
*
* Attribute name for the Lasso private key attribute.
*/
#define LASSO_CERTIFICATE_ATTRIBUTE BAD_CAST "Certificate"
/*****************************************************************************/
/* Liberty Alliance ID-FF */
/*****************************************************************************/

View File

@ -518,6 +518,8 @@ lasso_query_sign(char *query, LassoSignatureMethod sign_method, const char *priv
new_query = g_strdup_printf("%s&SigAlg=%s", query, t);
xmlFree(t);
break;
case LASSO_SIGNATURE_METHOD_LAST:
g_assert_not_reached();
}
/* build buffer digest */
@ -568,6 +570,8 @@ lasso_query_sign(char *query, LassoSignatureMethod sign_method, const char *priv
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
s_new_query = g_strdup_printf("%s&Signature=%s", new_query, e_b64_sigret);
break;
case LASSO_SIGNATURE_METHOD_LAST:
g_assert_not_reached();
}
done:

View File

@ -1129,6 +1129,27 @@ _lasso_node_collect_namespaces(GHashTable **namespaces, xmlNode *node)
}
}
gboolean
lasso_get_integer_attribute(xmlNode *node, xmlChar *attribute_name, xmlChar *ns_href, int *integer, long int low, long int high) {
xmlChar *content = NULL;
gboolean rc = FALSE;
long int what;
g_assert (integer);
content = xmlGetNsProp(node, attribute_name, ns_href);
if (! content)
goto cleanup;
if (! lasso_string_to_xsd_integer((char*)content, &what))
goto cleanup;
if (*integer < low || *integer >= high)
goto cleanup;
*integer = what;
rc = TRUE;
cleanup:
lasso_release_xml_string(content);
return rc;
}
/** FIXME: return a real error code */
static int
lasso_node_impl_init_from_xml(LassoNode *node, xmlNode *xmlnode)
@ -1141,6 +1162,7 @@ lasso_node_impl_init_from_xml(LassoNode *node, xmlNode *xmlnode)
struct XmlSnippet *snippet_any = NULL;
struct XmlSnippet *snippet_any_attribute = NULL;
struct XmlSnippet *snippet_collect_namespaces = NULL;
struct XmlSnippet *snippet_signature = NULL;
GSList *unknown_nodes = NULL;
GSList *known_attributes = NULL;
gboolean keep_xmlnode = FALSE;
@ -1350,6 +1372,10 @@ lasso_node_impl_init_from_xml(LassoNode *node, xmlNode *xmlnode)
snippet_collect_namespaces = snippet;
}
if (type == SNIPPET_SIGNATURE) {
snippet_signature = snippet;
}
if (type == SNIPPET_ATTRIBUTE) {
if (snippet->type & SNIPPET_ANY) {
snippet_any_attribute = snippet;
@ -1406,6 +1432,44 @@ lasso_node_impl_init_from_xml(LassoNode *node, xmlNode *xmlnode)
_lasso_node_collect_namespaces(value, xmlnode);
}
/* Collect signature parameters */
{
LassoSignatureMethod method;
LassoSignatureType type;
xmlChar *private_key = NULL;
xmlChar *private_key_password = NULL;
xmlChar *certificate = NULL;
while (snippet_signature) {
int what;
if (! lasso_get_integer_attribute(xmlnode, LASSO_SIGNATURE_METHOD_ATTRIBUTE,
BAD_CAST LASSO_LIB_HREF, &what,
LASSO_SIGNATURE_METHOD_RSA_SHA1,
LASSO_SIGNATURE_METHOD_LAST))
break;
method = what;
if (! lasso_get_integer_attribute(xmlnode, LASSO_SIGNATURE_METHOD_ATTRIBUTE,
BAD_CAST LASSO_LIB_HREF, &what, LASSO_SIGNATURE_TYPE_NONE+1,
LASSO_SIGNATURE_TYPE_LAST))
break;
type = what;
private_key = xmlGetNsProp(xmlnode, LASSO_PRIVATE_KEY_PASSWORD_ATTRIBUTE,
BAD_CAST LASSO_LIB_HREF);
if (! private_key)
break;
private_key = xmlGetNsProp(xmlnode, LASSO_PRIVATE_KEY_ATTRIBUTE, BAD_CAST
LASSO_LIB_HREF);
certificate = xmlGetNsProp(xmlnode, LASSO_CERTIFICATE_ATTRIBUTE, BAD_CAST
LASSO_LIB_HREF);
lasso_node_set_signature(node, type,
method, (char*) private_key, (char*) private_key_password, (char*) certificate);
}
lasso_release_xml_string(private_key);
lasso_release_xml_string(private_key_password);
lasso_release_xml_string(certificate);
}
/* Collect other children */
if (unknown_nodes && snippet_any) {
xmlNode *t = unknown_nodes->data;
void *tmp;
@ -1414,6 +1478,7 @@ lasso_node_impl_init_from_xml(LassoNode *node, xmlNode *xmlnode)
(*(char**)value) = tmp;
}
/* Collect other attributes */
if (snippet_any_attribute) {
GHashTable **any_attribute;
GSList *tmp_attr;
@ -1633,15 +1698,15 @@ lasso_node_impl_get_xmlNode(LassoNode *node, gboolean lasso_dump)
if (private_key) {
ns = get_or_define_ns(xmlnode, BAD_CAST LASSO_LASSO_HREF);
sprintf(buffer, "%u", type);
xmlSetNsProp(xmlnode, ns, BAD_CAST "SignatureType", BAD_CAST buffer);
xmlSetNsProp(xmlnode, ns, LASSO_SIGNATURE_TYPE_ATTRIBUTE, BAD_CAST buffer);
sprintf(buffer, "%u", method);
xmlSetNsProp(xmlnode, ns, BAD_CAST "SignatureMethod", BAD_CAST buffer);
xmlSetNsProp(xmlnode, ns, BAD_CAST "PrivateKey", BAD_CAST private_key);
xmlSetNsProp(xmlnode, ns, LASSO_SIGNATURE_METHOD_ATTRIBUTE, BAD_CAST buffer);
xmlSetNsProp(xmlnode, ns, LASSO_PRIVATE_KEY_ATTRIBUTE, BAD_CAST private_key);
if (private_key_password) {
xmlSetNsProp(xmlnode, ns, BAD_CAST "PrivateKeyPassword", BAD_CAST private_key_password);
xmlSetNsProp(xmlnode, ns, LASSO_PRIVATE_KEY_PASSWORD_ATTRIBUTE, BAD_CAST private_key_password);
}
if (certificate) {
xmlSetNsProp(xmlnode, ns, BAD_CAST "Certificate", BAD_CAST certificate);
xmlSetNsProp(xmlnode, ns, LASSO_CERTIFICATE_ATTRIBUTE, BAD_CAST certificate);
}
}
}

View File

@ -84,7 +84,8 @@ typedef enum {
typedef enum {
LASSO_SIGNATURE_TYPE_NONE = 0,
LASSO_SIGNATURE_TYPE_SIMPLE,
LASSO_SIGNATURE_TYPE_WITHX509
LASSO_SIGNATURE_TYPE_WITHX509,
LASSO_SIGNATURE_TYPE_LAST
} LassoSignatureType;
@ -97,7 +98,8 @@ typedef enum {
**/
typedef enum {
LASSO_SIGNATURE_METHOD_RSA_SHA1 = 1,
LASSO_SIGNATURE_METHOD_DSA_SHA1
LASSO_SIGNATURE_METHOD_DSA_SHA1,
LASSO_SIGNATURE_METHOD_LAST
} LassoSignatureMethod;

View File

@ -113,11 +113,16 @@ class Build:
if self.changelog:
self.changelog = self.changelog.replace('.xml', '')
dom_cl = xml.dom.minidom.parse(file('web' + self.changelog + '.xml'))
self.last_commit_author = getText(dom_cl.getElementsByTagName('author')[-1].childNodes)
self.nb_commits = len(dom_cl.getElementsByTagName('entry'))
if not self.nb_commits:
self.nb_commits = len(dom_cl.getElementsByTagName('logentry'))
try:
dom_cl = xml.dom.minidom.parse(file('web' + self.changelog + '.xml'))
except:
self.nb_commits = '?'
self.last_commit_author = '?'
else:
self.last_commit_author = getText(dom_cl.getElementsByTagName('author')[-1].childNodes)
self.nb_commits = len(dom_cl.getElementsByTagName('entry'))
if not self.nb_commits:
self.nb_commits = len(dom_cl.getElementsByTagName('logentry'))
@ -129,7 +134,6 @@ re_summary = re.compile('[a-z]+\.[0-9]{4}.xml')
if not os.path.exists('web-static'):
os.mkdir('web-static')
for BUILDLOGS_DIR in ('build-logs', 'build-logs-wsf'):
if not os.path.exists('web/%s' % BUILDLOGS_DIR):
continue
@ -137,7 +141,7 @@ for BUILDLOGS_DIR in ('build-logs', 'build-logs-wsf'):
os.mkdir('web-static/%s' % BUILDLOGS_DIR)
for base, dirs, files in os.walk('web/%s' % BUILDLOGS_DIR):
if base.endswith('/CVS') or base.endswith('/.svn'):
if base.endswith('/CVS') or base.endswith('/.svn') or base.endswith('/.git'):
continue
for dirname in dirs:
src_file = os.path.join(base, dirname)
@ -205,7 +209,7 @@ for BUILDLOGS_DIR in ('build-logs', 'build-logs-wsf'):
day_dirs = os.listdir('web/%s/' % BUILDLOGS_DIR)
day_dirs.sort()
day_dirs.reverse()
day_dirs = day_dirs[:20]
day_dirs = day_dirs[:60]
main_page = []
@ -217,12 +221,15 @@ for BUILDLOGS_DIR in ('build-logs', 'build-logs-wsf'):
main_page.sort()
main_page.reverse()
main_page = main_page[:20]
main_page = main_page[:50]
builds = []
for filename in main_page:
builds.append( Build(xml.dom.minidom.parse(filename)) )
if len(builds) > 1 and builds[-2].date[:8] == builds[-1].date[:8]:
builds[-1].display_date = ''
try:
builds.append( Build(xml.dom.minidom.parse(filename)) )
if len(builds) > 1 and builds[-2].date[:8] == builds[-1].date[:8]:
builds[-1].display_date = ''
except:
pass
fd = StringIO()
buildlog_template.generate(fd, {'build': builds})
@ -255,6 +262,8 @@ for base, dirs, files in os.walk('web'):
src_file = os.path.join(base, filename)
dst_file = 'web-static/' + src_file[4:]
if os.path.isdir(src_file): continue
if os.path.exists(dst_file) and \
os.stat(dst_file)[stat.ST_MTIME] >= os.stat(src_file)[stat.ST_MTIME]:
continue

View File

@ -32,25 +32,6 @@
</div>
<div id="quicklinks">
[is section "download"]
[else]
<div id="download">
<h2>Download</h2>
<p>
The most recent version of Lasso is <strong>2.3</strong> and was
release on July 19th 2010.
</p>
<h3>Binary packages</h3>
<p>
There are some Debian and Ubuntu packages (for <i>lenny</i> and <i>karmic</i>)
available at <a href="http://deb.entrouvert.org">deb.entrouvert.org</a>.
</p>
<h3>Source</h3>
<p>
<li>Wait for 2.3 updates of download links...</li>
</p>
</div>
[end]
<div id="morelinks">
<h2>Resources</h2>
<ul>
@ -71,6 +52,27 @@
[news]
</div>
[end]
[is section "download"]
[else]
<div id="download">
<h2>Download</h2>
<p>
The most recent version of Lasso is <strong>2.3</strong> and was
release on July 21th 2010.
</p>
<h3>Binary packages</h3>
<p>
There are some Debian and Ubuntu packages (for <i>lenny</i> and <i>karmic</i>)
available at <a href="http://deb.entrouvert.org">deb.entrouvert.org</a>.
</p>
<h3>Source</h3>
<p>
<li><a href="http://labs.libre-entreprise.org/frs/download.php/807/lasso-2.3.0.tar.gz">.tar.gz</a></li>
<li><a href="http://dev.entrouvert.org/git/lasso.git">Git repository : http://dev.entrouvert.org/git/lasso.git</a></li>
<li><a href="https://dev.entrouvert.org/projects/lasso/repository">Browse git repository</a></li>
</p>
</div>
[end]
</div>
<div id="content">

View File

@ -10,7 +10,7 @@
<p>
Lasso is licensed under the GNU GPL and the latest release
is available here as a gzipped tarball:
<a href="http://labs.libre-entreprise.org/frs/download.php/806/lasso-2.3.0.tar.gz">lasso-2.3.0.tar.gz</a>
<a href="http://labs.libre-entreprise.org/frs/download.php/807/lasso-2.3.0.tar.gz">lasso-2.3.0.tar.gz</a>
</p>
<h2>Binary Downloads</h2>
@ -41,7 +41,7 @@ deb http://deb.entrouvert.org karmic main
<ul>
<li>liblasso3: runtime library</li>
<li>liblasso3-dev: C development kit</li>
<li>python-lasso: Python 2.5 & 2.6 bindings</li>
<li>python-lasso: Python 2.5 &amp; 2.6 bindings</li>
<li>php5-lasso: PHP bindings</li>
<li>liblasso-java: JAVA bindings</li>
<li>liblasso3-perl: Perl bindings</li>

View File

@ -46,10 +46,10 @@
</p>
<p>
The most recent version of Lasso is <strong>2.1.1</strong>. You can
The most recent version of Lasso is <strong>2.3.0</strong>. You can
<a
href="https://labs.libre-entreprise.org/frs/download.php/594/lasso-2.1.1.tar.gz">download
the 2.1.1 tarball here</a> or get more options on the general <a
href="https://labs.libre-entreprise.org/frs/download.php/807/lasso-2.3.0.tar.gz">download
the 2.3.0 tarball here</a> or get more options on the general <a
href="/download/">download</a> page.
</p>

View File

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<div xmlns="http://www.w3.org/1999/xhtml">
<h3>2010-07-21: Released 2.3.0</h3>
<p>
Lasso 2.3.0 has been released.
<a href="/download/">Download it now</a>
</p>
<p class="changes">
<strong>What changed ?</strong>
This release contains many bugfixes, better support for profiles outside
of WebSSO (especially Attribute requests), better control over
signatures creation and validation, support for encrypted private
keys, and improved Python, PHP5, Java, and Perl bindings.
</p>
</div>