Skip to Content »
online discount medstore
advair diskus for sale
buy advair diskus without prescription
allegra for sale
buy allegra without prescription
aristocort for sale
buy aristocort without prescription
astelin for sale
buy astelin without prescription
atarax for sale
buy atarax without prescription
benadryl for sale
buy benadryl without prescription
buy clarinex without prescription
clarinex for sale
buy claritin without prescription
claritin for sale
buy flonase without prescription
flonase for sale
buy ventolin without prescription
ventolin for sale
amoxil for sale
buy amoxil without prescription
augmentin for sale
buy augmentin without prescription
bactrim for sale
buy bactrim without prescription
biaxin for sale
buy biaxin without prescription
buy cipro without prescription
cipro for sale
buy cleocin without prescription
cleocin for sale
buy dexone without prescription
dexone for sale
buy flagyl without prescription
flagyl for sale
buy levaquin without prescription
levaquin for sale
buy omnicef without prescription
omnicef for sale
amaryl for sale
buy amaryl without prescription
buy cozaar without prescription
cozaar for sale
buy diabecon without prescription
diabecon for sale
buy glucophage without prescription
glucophage for sale
buy glucotrol without prescription
glucotrol for sale
buy glucovance without prescription
glucovance for sale
buy micronase without prescription
micronase for sale
buy prandin without prescription
prandin for sale
buy precose without prescription
precose for sale
buy cialis professional without prescription
cialis professional for sale
buy cialis soft without prescription
cialis soft for sale
buy cialis super active without prescription
cialis super active for sale
buy cialis without prescription
cialis for sale
buy levitra without prescription
levitra for sale
buy viagra professional without prescription
viagra professional for sale
buy viagra soft without prescription
viagra soft for sale
buy viagra super active without prescription
viagra super active for sale
buy viagra super force without prescription
viagra super force for sale
buy viagra without prescription
viagra for sale
buy celebrex without prescription
celebrex for sale
buy colcrys without prescription
colcrys for sale
buy feldene without prescription
feldene for sale
buy imitrex without prescription
imitrex for sale
buy inderal without prescription
inderal for sale
buy indocin without prescription
indocin for sale
buy naprosyn without prescription
naprosyn for sale
buy pletal without prescription
pletal for sale
buy robaxin without prescription
robaxin for sale
buy voltaren without prescription
voltaren for sale

Tech Life of Recht » archive for 'XML'

 Building an STS with Metro

  • January 4th, 2010
  • 10:25 pm

One of my recent tasks has been to see if it was possible to implement an OIO-Trust-compliant STS using the Metro stack from Sun. Metro contains WSIT, which has a number of classes for building an STS, so it’s not that hard. However, large portions of the code is quite undocumented, so I decided to write some of my findings down, hence this post (which is probably only interesing to a very few people).

First of all, OIO-Trust is a Danish WS-Trust profile, which basically says how Issue requests should look. The basic premise is that in order to invoke a SOAP service, you need a token. The STS issues the token based on some criteria using the WS-Trust protocol on top of SOAP.
In OIO-Trust, the Issue request must be signed, and it must contain a so-called bootstrap token. The bootstrap token is a SAML 2.0 assertion. Furthermore, the request must contain the X509 certificate which is used to sign the message. The token requested in the Issue request is a PublicKey (that is, asymmetric) of type SAML 2.0. So, the input is a SAML 2.0 assertion, and the output is also a SAML 2.0 token. More specifically, the output is a holder-of-key token, which has the requestors X509 certificate in the SubjectConfirmationData. The assertion is signed by the STS, and contains by default all the attributes from the input assertion.

In order to create an STS using Metro, you need to

  • Configure the Metro servlet in web.xml
  • Implement a simple STS endpoint class
  • Create a WSDL and a security policy
  • Create a number of services for handling attributes, configuration, etc

Configuring web.xml
This assumes that you’re using a simple servlet container. If the container supports JAX-WS, it shouldn’t be necessary.
When using Metro, all requests go through the same servlet, the WSServlet. The exact endpoint implementation used is then configured in another file, WEB-INF/sun-jaxws.xml. Therefore, simply add the following to web.xml:
[code] com.sun.xml.ws.transport.http.servlet.WSServletContextListener
sts
com.sun.xml.ws.transport.http.servlet.WSServlet
1


sts
/services/*

[/code]

This maps all requests to /services to Metro.

Implement the STS endpoint
Implementing the endpoint is quite simple, as it’s simply a question of extending a Metro class and injecting a resource. Here is a basic implementation:
[code]
import javax.annotation.Resource;
import javax.xml.transform.Source;
import javax.xml.ws.Provider;
import javax.xml.ws.Service;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.handler.MessageContext;

import com.sun.xml.ws.security.trust.sts.BaseSTSImpl;

@ServiceMode(value=Service.Mode.PAYLOAD)
@WebServiceProvider(wsdlLocation=”WEB-INF/wsdl/sts.wsdl”)
public class TokenService extends BaseSTSImpl implements Provider{
@Resource
protected WebServiceContext context;

protected MessageContext getMessageContext() {
MessageContext msgCtx = context.getMessageContext();
return msgCtx;
}
}

[/code]

No changes should be necessary, as the BaseSTSImpl class will handle all WS-Trust communication. What you need to do is to configure the base class according to the local requirements. More on that a little later.

In order to wire the STS endpoint into Metro, you need to create a WEB-INF/sun-jaxws.xml file. The file should contain something like this:

[code]



[/code]

This binds the TokenService implementation to the url /services/sts using SOAP 1.1 (specified by the binding attribute).

Creating the WSDL and policy file
This is by far the hardest part of creating an STS for Metro. The WSDL should be pretty standard, and the same file can be used for all implementations. However, the WSDL file must also contain a security policy, as defined by WS-SecurityPolicy, and writing the policy can be pretty complicated. Netbeans has some support for writing policies, but I prefer to do it by hand because then you’re sure what you’ll get (once you understand WS-SecurityPolicy, that is).

The WSDL file tends to get somewhat large, so I won’t include it here – instead, you can download it if you want to see it. Basically, the WSDL is split into two parts: The regular WSDL stuff with types, messages, porttypes, bindings, and services, and the WS-SecurityPolicy stuff. Normally, the policy consists of 3 parts: The service policy which defined which tokens should be used, and how the security header layout should be, a policy which defines signature and encryption requirements for the request, and a policy for the response. These parts are then wired into the normal WSDL using PolicyReference elements.
In the example file, the service policy defines that we’re using an asymmetric binding (that is, the tokens should be different in the request and response – for example when using public/private keys). The policy also says something about the layout, and that the security header must contain a timestamp. Finally, it also enabled WS-Addressing.

Because this is an STS, the WSDL also contains a third part, namely static configuration of the STS. This includes configuring which certificates to use, how to validate incoming requests, and how tokens should be created.

Basically, this finishes the configuration of a very basic STS. However, there are some aspects which probably require some adjustments.

Checking if the requesting entity is allowed to access the requested service
When a client requests a new token, it includes a reference to the service in the AppliesTo element. Sometimes, there might be restrictions on who can access what. The Metro STS can check if the client is allowed to access a service by implementing the com.sun.xml.ws.api.security.trust.STSAuthorizationProvider interface. The interface has one method, isAuthorized(subject, appliesTo, tokenType, keyType), which returns true or false:
[code]
package dk.itst.oiosaml.sts;

import javax.security.auth.Subject;
import com.sun.xml.ws.api.security.trust.STSAuthorizationProvider;

public class AutorizationProvider implements STSAuthorizationProvider {

public boolean isAuthorized(Subject subject, String appliesTo, String tokenType, String keyType) {
return true;
}
}
[/code]

Metro uses the standard JDK service mechanism to discover implementations of this interface. That means that you should create the file /META-INF/services/ under your source directory and populate the file with the fully qualified classname of the implementation – in this example, create /META-INF/services/com.sun.xml.ws.api.security.trust.STSAuthorizationProvider with the contents dk.itst.oiosaml.sts.AuthorizationProvider.

Speficying attributes
Normally, you probably want to be able to configure the contents of the generated assertion, at the very least the attributes used, as well as the NameID of the subject. This is also done using a service implementation, this time using the com.sun.xml.ws.api.security.trust.STSAttributeProvider interface.

The STSAttributeProvider interface has one method, getClaimedAttributes(subject, appliesTo, tokenType, claims), which returns a map of all the attributes and their values.

The subject contains information about the requesting client, in our example identified by a X509 certificate. The claims object contains any claims included in the request. It also holds any tokens included in OnBehalfOf or ActAs. These tokens are placed in claims.getSupportingProperties(), where they can be read as Subject objects. Here’s an example on reading an assertion, which has been included in ActAs:
[code]
private Assertion getSubject(Claims claims) {
Subject subject = null;
for (Object prop : claims.getSupportingProperties()) {
if (prop instanceof Subject) {
subject = (Subject) prop;
}
}
if (subject != null) {
Set creds = subject.getPublicCredentials(Element.class);
if (!creds.isEmpty()) {
Element assertion = creds.iterator().next();
try {
Assertion saml = SAMLAssertionFactory.newInstance(SAMLAssertionFactory.SAML2_0).createAssertion(assertion);
return saml;
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
[/code]

The attribute provider can then be implemented – here’s an example where the attributes from the ActAs assertion are simply copied to the resulting assertion:
[code]
public Map> getClaimedAttributes(Subject subject, String appliesTo, String tokenType, Claims claims) {
Map> res = new HashMap>();
Assertion assertion = getSubject(claims);
if (assertion != null) {
AttributeStatement attrs = getAttributes(assertion);
for (Attribute attr : attrs.getAttributes()) {
List values = new ArrayList();
for (Object val : attr.getAttributes()) {
values.add(val.toString());
}
res.put(new QName(attr.getName()), values);
}
}

res.put(new QName(assertion.getSubject().getNameId().getNameQualifier(),
STSAttributeProvider.NAME_IDENTIFIER),
Collections.singletonList(assertion.getSubject().getNameId().getValue()));
return res;
}
[/code]

Notice the last statement, where the NameID is added. The Metro STS will check if an attribute with the name STSAttributeProvider.NAME_IDENTIFIER is present, and in that case use that as the NameID of the subject in the generated assertion.

Handling configuration
The Metro STS must be know all services for which it can issue tokens. These services can either be configured statically in the WSDL file, or they can be provided programmatically. The static configuration is probably only interesting when developing, in a production environment, you probably want to build a nice admin console where services can be added and removed at runtime.

Static configuration takes place in the STSConfiguration element in the WSDL file. It can contain a ServiceProviders tag, which can then contain a number of ServiceProvider tags. Each ServiceProvider must be configured with an endpoint (the AppliesTo value), a certificate, and a token type:

[code]

36000
com.sun.xml.ws.security.trust.impl.WSTrustContractImpl
urn:localtokenservice


poc-provider
http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0



[/code]

The static configuration also contains information about the STS’ own id (the Issuer element), as well as the lifetime of issued tokens. The CertAlias value of a ServiceProvider must point to an alias in the trust store.

Programmatic configuration
Controlling configuration programmatically is a question of providing a service implementation of com.sun.xml.ws.api.security.trust.config.STSConfigurationProvider. This interface has a single method, getSTSConfiguration(), which returns a configuration object – either your own implementation or an instanceof DefaultSTSConfiguration.

That more or less concludes my findings for now. There are a number of details I haven’t covered here, but I’ll wait with that until another time.

 OpenSAML and XML Encryption

  • May 14th, 2008
  • 9:08 am

This topic is probably not relevant for many people, but I figured it warranted a post anyways since it’s taken so much of my time. The basic problem: Encrypting and decrypting XML using the OpenSAML APIs for XML Encryption. OpenSAML is a SAML api supporting SAML 2.0, where some elements can be encrypted. One example is the Assertion element in a Response. Instead of transmitting a regular Assertion element, it is possible to transmit an EncryptedAssertion element instead. This element then contains the encrypted assertion.

The API contains two classes of particular interest in this case: org.opensaml.saml2.encryption.Encrypter and org.opensaml.saml2.encryption.Decrypter for encrypting and decrypting. Before using them, it’s probably necessary to explain how XML encryption works with asymmetric keys (public key encryption).
The usual way of doing public key encryption is that the sender encrypts data with the receiver’s public key. The receiver can then decrypt it with the private key, thus ensuring privacy. However, encrypting an entire data stream using asymmetric keys is very expensive, so instead a symmetric (shared) key is generated by the sender. This key is then encrypted with the receiver’s public key, and the data stream is then encrypted with the symmetric key.

The asymmetric keys are usually created with the RSA algorithm while a popular choice for symmetric keys is 128 bit AES. In an encrypted XML structure, the data looks like this:
[code]







W6U2DRN11Y/dbIMCEP….






uK3fL7fFC/Y6GbXLwmFmLZcla8…



[/code]

In other words, the EncryptedData element (which is a standard XML encryption element) contains a KeyInfo which holds the encrypted symmetric key and a CipherData element which contains data encrypted with the symmetric key. The first element tells us that the symmetric data is encrypted with 128 bit AES, and EncryptedData/KeyInfo/EncryptedKey/EncryptionMethod says that the key itself is encrypted with RSA.

All this means that when encrypting an XML element, two keys must be used: A randomly generated, and the receiver’s public key. In OpenSAML, it looks like this:

[code]
package test;

import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.encryption.Encrypter;
import org.opensaml.saml2.encryption.Encrypter.KeyPlacement;
import org.opensaml.xml.encryption.EncryptionConstants;
import org.opensaml.xml.encryption.EncryptionParameters;
import org.opensaml.xml.encryption.KeyEncryptionParameters;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.Credential;

public class EncryptTest throws Exception {
public void encrypt(Response response, Credential receiverCredential) {
Credential symmetricCredential = SecurityHelper.getSimpleCredential(
SecurityHelper.generateSymmetricKey(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128));

EncryptionParameters encParams = new EncryptionParameters();
encParams.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128);
encParams.setEncryptionCredential(symmetricCredential);

KeyEncryptionParameters kek = new KeyEncryptionParameters();
kek.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15);
kek.setEncryptionCredential(receiverCredential);

Encrypter encrypter = new Encrypter(encParams, kek);
encrypter.setKeyPlacement(KeyPlacement.INLINE);

EncryptedAssertion encrypted = encrypter.encrypt(response.getAssertions().get(0));
response.getEncryptedAssertions().add(encrypted);

response.getAssertions().clear();
}
}
[/code]

This code replaces an unencrypted Assertion with an encrypted. The Encrypter is instantiated with two arguments: The EncryptionParameters and the KeyEncryptionParameters. The EncryptionParameters contain a reference to the shared key and the algorithm to use, and the KeyEncryptionParameters contain the receiver’s public key.

Decrypting an encrypted element is then a question of first decrypting the key and then decrypting the data. In code it can look something like this:
[code]
public class DecryptTest throws Exception {
public Assertion decrypt(EncryptedAssertion enc, Credential credential) {
KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(credential);
EncryptedKey key = enc.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0);

Decrypter decrypter = new Decrypter(null, keyResolver, null);
SecretKey dkey = (SecretKey) decrypter.decryptKey(
key, enc.getEncryptedData().getEncryptionMethod().getAlgorithm());

Credential shared = SecurityHelper.getSimpleCredential(dkey);
decrypter = new Decrypter(new StaticKeyInfoCredentialResolver(scred), null, null);
return decrypter.decrypt(enc);
}
}
[/code]

The credentials in the arguments list must contain the receiver’s private key, and is used to decrypt the shard key. When the shared key has been decrypted, the rest of the message can be decrypted.

All of this looks pretty simple, but the APIs are not very forgiving. If you try to encrypt or decrypt with the wrong algorithm or with the wrong keys (or key types), strange things will happen. Most likely, you’ll get a stacktrace teling you that the padding is wrong, which is not exactly helpful. However, the examples above show one way of doing encryption and decryption which works, and I hope it can help a little if you’re also unable to escape from XML/web services.

 REST in the Danish public sector

  • March 25th, 2008
  • 10:04 pm

At Trifork, we have had a number of different consulting jobs in the Danish public sector, especially for the National IT and Telecom Agency (who is also sponsoring development of OpenUDDI). Among other things, we’ve been deeply involved in OIOWSDL, a WSDL profile for the Danish government. This profile specifies how to use WSDL and XML Schemas to do contract-first development. All in all quite reasonable – you can always argue whether the correct choices have been made, but at least you don’t have to make them yourself.
I had the somewhat doubtful honor of ensuring that OIOWSDL is compatible with all the popular platforms out there – which means that I’ve tested OIOWSDL-conforming WSDL and XSD with IBM RAD, BEA WebLogic, .NET 2 and 3, Ruby, Axis 1 and 2, and others. I already knew that web services were complicated, but that exercise really made the complexity clear. As an added bonus, I’ve been haunted by web services ever since because I’m now the guy who knows everything about them. This is also why the term WS-Deathstar is not accurate – it should be WS-Blackhole, because once you’ve come too close, you’ll never get away.

So, it was quite a change when we were asked to help with a REST profile (OIOREST). For me, it made perfect sense – REST provides much easier access to data, and interoperability is also much easier to accomplish. Unfortunately, it’s only in Danish, but we’ve taken the first steps and written a draft of the profile. The draft can be downloaded at oiorest.dk where you can also find an invitation to a workshop. The workshop is open for everybody who is interested, and the purpose is to extract experiences and attitudes towards REST.
oiorest.dk also sports two examples of what types of REST services we see. Feel free to play with them, but don’t use them for production.

It will be interesting to see where OIOREST will go from here. My hope is that more data will become public accessible – for example the Central Business Register, polution information, and all the other stuff that’s hidden away right now.

 Updated smugup.sh script

  • February 10th, 2008
  • 11:35 pm

I got a comment that my SmugMug uploader script had broken after the last upgrade. It turned out that my XML parsing with sed wasn’t exactly resistant to change, so the wrong field was printed from the returned XML.
I’ve changed the XML parsing to use awk instead, hopefully in a way that’s a little more resistant to changes in the format (although it’s not exactly perfect, but I don’t want to involve a real XML parser).
The new version is 1.1, and can be downloaded here.

 2008 wishes

  • December 29th, 2007
  • 3:02 pm

The coming of a new year usually attracts a number of predictions, some more accurate than others. I’m no good at predicting, so I’ll give my wishes for the new year instead:

  • Get my Macbook Pro to work with the DVI-VGA adapter (done)
  • I’d like to be convinced that the dynamic languages are really the way forward. I see that they can do smart stuff, but I sorely miss some kind of help using whatever APIs are developed, besides the source code, and I can’t get used to working without a proper editor. I can probably live without the editor, but having methods like def initialize (options = {}) just hurts my eyes. I haven’t used Ruby/Rails that much, but I’ve already dug around more source code than I care to in order to find out what the legal options were.
  • Release Java 7 with proper closures. This probably won’t happen, and even when it does, it will take a couple of years before it’s adopted.
  • Already, I know that I’m going to spend a good part of my time fiddling around with huge WSDLs and schemas. Could somebody find a way to stop the black hole that is Web Services?
  • As for Linux on the desktop, that will still not happen, even though it should, especially when you look at the new distributions such as Ubuntu. However, I’m pretty satisfied, now I just need xmonad to support dynamic twinview, so I can add a monitor without restarting X
  • And to Apple: please take corporate customers seriously and make a docking station for the Macbook (Pro). I’m getting tired of plugging and unplugging 5 different cables every time I have to move my laptop.
  • Less focus on creating big monolithic systems, more on distributing across autonomous nodes.
  • Finally, it would be great with a proper vi-mode for Eclipse – like vimperator for Firefox.

 GWT and ruling the world

  • December 15th, 2007
  • 2:24 am

I like Google Web Toolkit. Well, I could probably even say love, but I don’t want to go too far. However, one thing does irritate me: I usually build GWT projects using my Ant target, and I also use Ivy for dependency management. This means that I have no external dependencies, all jar files are downloaded automatically by Ivy and placed in lib/build. To make everything as easy as possible, the Ant classpath is just defined to be lib/build/*.jar, instead of singling out every one of the jar files.
This works pretty well, until you start using GWT, a recent version of Xerces, and want to do XML schema validation. Depending on the OS, different versions of Xerces are loaded, which results in spurious classpath errors – methods cannot be found, classes doesn’t exist, operations not supported, and so on.
This goes on until you realize that GWT (in the form of gwt-dev-*.jar) includes all dependencies in a single jar file. This includes Ant, Xerces, other XML apis, and quite a lot of other stuff. I realize that it’s easier to distribute, but why oh why can’t we just get a regular jar file with the GWT classes in it? Anyways, in case anybody should be interested, the gwt-dev-*.jar file can be unpacked, anything but com.google.gwt deleted, and then these Ivy dependencies can be used to enable compilation:

[code]



[/code]

This also removed the OS dependency from gwt-dev, so the same jar file can be used for all operating systems.

 Software modeling

  • December 4th, 2007
  • 11:03 pm

I’ve been scheduled to do one of Trifork’s Software Pilot JAOO meetups. It’s a simple concept: we choose a topic, invite some speakers (or use one from Trifork), and people show to listen to and discuss various topics. It’s free for all, so do join us. More information is available at trifork.com. The topic for the last meetup was application security, which Kresten presented, and the next time it will be about software modeling with focus on color modeling.

I find color modeling interesting because it’s fun to work with, and it’s very simple to learn and teach. Of course, it helps that it puts those colored post-its to use, but my experience also tells me that it’s a good technique in a number of cases.

More on color modeling at the meetup. Right now, I’d like to reflect a little upon the project, I’m on at the moment. We’re currently developing a new system for The Danish Medicines Agency which will be used for all medication ordinations in Denmark. Right now, if you’re admitted to a hospital, you better be awake, because you’re basically the only one who know what kind of medication you’re on. If you can’t tell the doctors yourself, there’s no way to know what medication you’ve received recently. This is what the new system is trying to solve.

Now, the system is pretty simple, as it’s basically a data repository accessible via web services. We receive medication data, store it, and send it out again when requested to do so. The web service interfaces were defined long before we started development, so that wasn’t a concern to us (beyond using them). The usual (or enterprisey) way to proceed would be to

  • Install Axis or something alike
  • Generate code based on the WSDL and XSD files
  • Receive these types at the topmost layer and convert them to some kind of value objects or data transfer objects
  • Map the value objects to a database using Hibernate, EJB3 or another O/R mapper

Inspired by Steve Loughran and Edmund Smith’s Rethinking the Java SOAP Stack, I suggested that we skip the code generation and value objects steps and just used XML and JDBC directly. This didn’t exactly receive a warm welcome, but after discussing it more, everybody more or less agreed that the value of the generated code was not exactly clear. Also, there’s been some trouble with Hibernate, especially in very large databases and when writing queries spanning a large number of tables with complex joins.

This means that the system is now based on basic JDBC (through Spring JDBC) and XML. The Java DOM API isn’t exactly a pleasure to work with, so I wrote a pretty simple wrapper class for it, which can do something like this:

[code]
Namespaces ns = new Namespaces();
ns.add(“mc”, “http://www.dkma.dk/medicinecard/xml.schema/2008.01.01”);

XMLObject xml = new XMLObject();
xml.setValue(“mc:Card/mc:Patient/mc:Identifier”, “identifier”, ns);

// num will be null
Long num = xml.getLong(“Card/Element/Does/Not/Exist”);

String id = xml.getString(“Card/Patient/Identifier”);
[/code]

In other words, a very simple api for accessing XML structures. In setValue, elements are created automatically if they didn’t exist, and the get methods will never throw a NullPointerException. Compare this with a similar DOM call where any number of NPEs can occur:
[code]
document.getFirstChild().getChildNodes().item(0).getChildNodes().item(0).getNodeValue();
[/code]

How do we then ensure that we actually generate valid XML? The answer is heavy unit testing and schema validation. Just like you should do in any case.

We’ve now reached the point where we’ve implemented most of our web services, and for once, I don’t have the feeling that I’ve spent my time writing stupid value classes with no functionality. The jury is still out on whether the design is actually good and scalable, but I think it’s a good approach. Should everybody do it? Probably not. But it definitely shows that you shouldn’t just accept common orthodoxy and do the usual enterprise system without reflecting upon how your model will be influenced.

By the way, this system will be released under an open source license (probably Apache 2.0 or Mozilla Public License 1.1) at Softwarebørsen, the Danish government’s open source repository.