
The Access Control sublanguage is supported by a session entity that holds information regarding the currently logged in user. This session entity is called the securityContext and is configured as follows:
access control rules
{
principal is User with credentials name, password
}
This states that the User entity will be the entity representing a logged in user. The credentials are not used in the current implementation (the idea is to derive a default login template). The resulting generated session entity will be:
session securityContext
{
principal -> User
loggedIn :: Bool
}
Note that this principal declaration is used to enable the entire Access Control sublanguage.
Authentication must be added manually for now. Here are templates for login and logout that you can include in pages:
entity User
{
name :: String
password :: Secret
}
define login(){
var usr : User := User{};
form{
table{
row{ "Name: " input(usr.name) }
row{ "Password: " input(usr.password) }
row{ captcha() }
row{ action("Log In", login()) "" }
}
action login(){
var users : List<User> :=
select u from User as u
where (u._name = ~usr.name);
for (us : User in users ){
if (us.password.check(usr.password)){
securityContext.principal := us;
securityContext.loggedIn := true;
return viewUser(securityContext.principal);
}
}
securityContext.loggedIn := false;
return home();
}
}
}
define logout(){
"Logged in as " output(securityContext.principal)
form{
actionLink("Log Out", logout())
action logout(){
securityContext.loggedIn := false;
securityContext.principal := null;
return home();
}
}
}
When storing a secret property you need to create a digest of it:
newUser.password := newUser.password.digest();
This makes sure the secret property is stored encrypted. A digest can be compared with an entered string using the check method:
us.password.check(enteredpassword)
The default policy is to deny access to all pages and actions, the rules will determine what the conditions for allowing access are.
A simple rule protecting the editUser page to be only accessable by the user being edited looks like this:
rule page editUser(u:User){
u = securityContext.principal
}
An analysis of this rule:
Matching can be done a bit more freely using a trailing * as wildcard character, both in resource name and arguments:
rules page viewUs*(*){
true
}
When more fine-grained control is needed for rules, it is possible to specify nested rules. This implies that the nested rule is only valid for usage of that resource inside the parent resource. The allowed combinations are page - action, template - action, page - template. The next example shows nested rules for actions in a page:
rule page editDocument(d:Document){
d.author = securityContext.principal
rule action save(){
d.author = securityContext.principal
}
rule action cancel(){
d.author = securityContext.principal
}
}
This flexibility is often not necessary, and it is also inconvenient having to explicitly allow all the actions on the page, for these reasons some extra desugaring rules were added. When specifying a check on a page or template without nested checks, a generic action rules block with the same check is added to it by default. For example:
rule page editDocument(d:Document){
d.author = securityContext.principal
}
becomes
rule page editDocument(d:Document){
d.author = securityContext.principal
rule action *(*)
{
d.author = securityContext.principal
}
}
Predicates are functions consisting of one boolean expression, which allows reusing complicated expressions, or simply giving better structure to the policy implementation. An example of a predicate:
predicate mayViewDocument (u:User, d:Document){
d.author = securityContext.principal
|| u in d.allowedUsers
}
rule page viewDocument(d:Document){
mayViewDocument(securityContext.principal,d)
}
rule page showDocument(d:Document){
mayViewDocument(securityContext.principal,d)
}
Pointcuts are groups of resources to which conditions can be specified at once. Especially the open parts of the web application are easy to handle with pointcuts, an example:
pointcut openSections(){
page home(),
page createDocument(),
page createUser(),
page viewUser(*)
}
rule pointcut openSections(){
true
}
Pointcuts can also be used with parameters:
pointcut ownUserSections(u:User){
page showUser(u),
page viewUser(u),
page someUserTask(u,*)
}
rule pointcut ownUserSections(u:User){
u = securityContext.principal
}
A disabled page or action redirects to a very simple page stating access denied. Since this is not very user friendly, the visibility of navigate links and action buttons/links are automatically made conditional using the same check as the corresponding resource. An example conditional navigate:
if(mayViewDocument(securityContext.principal,d)){
navigate(viewDocument(d)){ "view " output(d.title) }
}
When using conditional forms it is often more convenient to put the form in a template, and control the visibility by a rule on the template.
Access Control policies that rely on extra data can create new or extend existing properties. An example of extending an entity is adding a set of users property to a document representing the users allowed access to that document:
extend entity Document{
allowedUsers -> Set<User>
}
Administration of Access Control in WebDSL is done by the normal WebDSL page definitions. All the data of the Access Control policy is integrated into the WebDSL application. An option is to incorporate the administration into an existing page with a template. This example illustrates the use of a template for administration:
define allowedUsersRow(document:Document){
row{ "Allowed Users:" input(document.allowedUsers) }
}
The template call for this template is added to the editDocument page:
table{
row{ "Title:" input(document.title) }
row{ "Text:" input(document.text) }
row{ "Author:" input(document.author) }
allowedUsersRow(document)
}
By using a template the Access Control can be disabled easily by not including the access control definitions and the template. The unresolved template definitions will give a warning but the page will generate normally and ignore the template call.