Found Pages

Access Control

Minimal Access Control Example

A minimal access control example is shown here.

Configuration of the Principal

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:

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 := this.principal != null
}

Note that this principal declaration is used to enable access control in the application.

It will also generate authentication() (both login and logout), login(), and logout() templates, and an authenticate function that takes the credentials as arguments and, if they are correct, returns true and sets the principal (only String/Email/Secret-type credential properties are allowed).

Authentication

Authentication can be added manually, instead of using the generated authentication templates. Here is a small example application with custom authentication:

 principal is User with credentials name, password

entity User{
  name :: String
  password :: Secret
}

define login(){
  var username := ""
  var password : Secret := ""
  form{ 
    label("Name: "){ input(username) }
    label("Password: "){ input(password) }
    captcha()
    submit login() {"Log In"}
  }
  action login(){
    validate(authenticate(username,password), 
      "The login credentials are not valid.");
    message("You are now logged in.");
  }
}

define logout(){
  "Logged in as " output(securityContext.principal)
  form{
    submitlink logout() {"Log Out"}
  }
  action logout(){
    securityContext.principal := null;
  }
}

define page root(){
  login()
  " "
  logout()
}

init{
  var u1 : User := 
    User{ name := "test" password := ("test" as Secret).digest() };
  u1.save();
}

access control rules

  rule page root(){
    true
  }

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)

Protecting Resources

The default policy is to deny access to all pages and ajax templates, the rules determine what the conditions for allowing access are. Regular templates are accessible by default, however, you can add additional access control rules on templates to limit their accessibility.

A simple rule protecting the editUser page to be only accessable by the user being edited looks like this:

    access control rules

      rule page editUser(u:User){
        u == principal     
      }

An analysis of this rule:

  • access control rules: a rules section is started with this declaration, multiple rules can follow. To go back to a normal section, use section some description.
  • rule: A keyword for Access Control rules
  • page: The type of resource being protected here, all the types available in the Access Control DSL for WebDSL are: page, action, template, function. The rules on pages protect the viewing of pages, action rules protect the execution of actions, template rules determine whether a template is visible in the including page, and finally rules on functions are lifted to the action invoking the function.
  • editUser: The name of the resource the rule will apply to.
  • (u:User): The arguments (if any) of the resource, the types of the arguments are used when matching. This also specifies what variables can be used in the checks.
  • u = principal: the check that determines whether access to this resource is allowed, this check is typechecked to be a correct boolean expression. The use of principal implies that the securityContext is not null and the user is logged in (these extra checks are generated automatically).

Matching can be done a bit more freely using a trailing * as wildcard character, both in resource name and arguments:

    rule 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 == principal
        rule action save(){
          d.author == principal
        }
        rule action cancel(){
          d.author == 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 == principal     
      }

becomes

      rule page editDocument(d:Document){
        d.author == principal
        rule action *(*)
        {
          d.author == principal
        }    
      }

Reuse in Access Control rules

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 == principal
        || u in d.allowedUsers
      }
      rule page viewDocument(d:Document){
        mayViewDocument(principal,d)
      }
      rule page showDocument(d:Document){
        mayViewDocument(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 == principal
      }

Note that each parameter must be used in each pointcut element, this indicates the value to be used as argument for the pointcut check. A wildcard * can follow to indicate that there may be additional arguments.

Inferring Visibility

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.

Using Entities

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

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.

Minimal Access Control Example

For explanation of access control elements, see Access control manual section

application minimalac

  entity User {
    name :: String
    password :: Secret
  }

  init{
    var u := User{ name := "1" password := ("1" as Secret).digest()  };
    u.save();
  }

  define page root(){
    authentication()
    " "
    navigate protectedPage() { "go" }
  }

  define page protectedPage(){ "access granted" }

  principal is User with credentials name, password

  access control rules

    rule page root(){true}
    rule page protectedPage(){loggedIn()}
For Loop in Action Code

Iterating a collection of entities or primitives can be done using a for loop. There are three types of for loop statements:

For

This type of for loop iterates the collection produced by expression e, which must contain elements of type t. The elements in the collection are accessible through identifier id.

The collection can be filtered:

    for(id:t in e){ stat* }
    for(id:t in e filter){ stat* }

ForAll

This for loop iterates all the entities in the database of type t. These can also be filtered. Note that it is more efficient to retrieve the objects using a filtering query and use the regular for loop above for iteration.

    for(id:t){ stat* }
    for(id:t filter){ stat* }

For Count

This for loop iterates the numbers from e1 to e2-1.

    for(id:Int from e1 to e2){ stat* }
App Configuration

In the application.ini file compile-, database- and deployment information is stored. Executing the webdsl command in a certain directory will look for a application.ini file to obtain compilation information. If no such file was found, it will start a simple wizard to create one.

Example application.ini:

backend=servlet
tomcatpath=/opt/tomcat
appname=hello
dbserver=localhost
dbuser=webdsluser
dbpassword=webdslpassword
dbname=webdsldb
dbmode=update
smtphost=localhost
smtpport=25
smtpuser=
smtppass=

Required Configuration

backend The back-end target platform of the application. Currently, the servlet back-end is only up-to-date.

appname The name of the application to compile. The compiler will look for a APPNAME.app file to compile. This name will also become the servlet name and show up as part of the URL. By renaming the generated APPNAME.war file to ROOT.war and then deploying it, the application name will not be in the URL.

tomcatpath This field should contain the root directory of the Tomcat installation. For example /opt/tomcat. It is used when executing ‘webdsl deploy’.

Database Configuration MySQL

dbmode This field indicates if the application should try to create tables in a database, or try to sync it with the existing schema to avoid loss of data. Valid values are create-drop, update, and false. Update can lead to unpredictable results if data model is changed too much, if data needs to be properly migrated, use Acoda instead. For production deployment use ‘export DBMODE=false’.

dbserver Location of the Mysql server, which will be used in the connection URL, e.g. ‘localhost’.

dbuser User to be used for connecting to the MySQL database.

dbpassword Password for the specified user.

dbname Database name, note that the database needs to exist when the application is run. The ‘webdsl’ script will try to create the database in the wizard, but manually creating it via command-line or MySQL Administrator is also possible.

Database Configuration H2 Database Engine in file

db Set db=h2 to enable H2 Database Engine instead of the default MySQL.

dbfile H2 database file, an empty file will be populated with tables automatically, when using ‘create-drop’ or ‘update’ db modes.

dbmode Same as for MySQL.

Database Configuration H2 Database Engine in memory

db Set db=h2mem to enable in-memory H2 Database Engine instead of the default MySQL.

dbmode Same as for MySQL, although effectively the tables are always dropped after a restart with in-memory database

Database Configuration through JNDI

db Set db=jndi to retrieve a JDBC resource from the application server, rather than providing the configuration in the web application.

dbjndipath JNDI path to the JDBC resource. On Apache Tomcat this is typically prefixed by ‘java:comp/env’. An example may be: ‘java:comp/env/jdbc/mydatabase’

dbmode Same as for MySQL.

Apart from settings in the application.ini, also a Context XML file must be provided for Apache Tomcat. An example may be:

<Context>
    <Resource name="jdbc/mydatabase"
        auth="Container"
        type="javax.sql.DataSource"
        driverClassName="com.mysql.jdbc.Driver"
        maxActivate="100" maxIdle="30" maxWait="10000"
        username="root" password="dbpassword"
        url="jdbc:mysql://localhost:3306/mydatabase?useServerPrepStmts=false&amp;characterEncoding=UTF-8&amp;useUnicode=true&amp;autoReconnect=true" />
</Context>

This XML file must be stored in: $TOMCAT_BASE/conf/Catalina/localhost/<appname>.xml

Email Configuration

smtphost SMTP host for sending email, e.g. smtp.gmail.com

smtpport SMTP port for sending email, e.g. 465

smtpuser SMTP username

smtppass SMTP password

smtpprotocol smtpprotocol=smtps [smtp/smtps] Use smtp or smtps as protocol.

smtpauthenticate smtpauthenticate=true [true/false] Authenticate with a username and password.

Search Configuration

indexdir set the index directory, default is /var/indexes.

searchstats Enable/disable search statistics, which can be displayed using template showSearchStats(). Default is false.

Optional Configuration

rootapp rootapp=true will deploy the application as root application, it will not have the application name prefix in the URL.

wikitext-hardwraps wikitext-hardwraps=true will enable so-called hard wraps in markdown. This way, each newline which isn’t followed by 2 white spaces is also rendered as new line. Default is false. See http://yellowgrass.org/issue/WebDSL/818

appurlforrenderwithoutrequest (as of WebDSL 1.3.0) Sets the URL to be used when links to pages are to be rendered outside a request. Normally, WebDSL will construct links using the request URL as a base. In case pages or templates with links are to be rendered outside a request (e.g. using a background task), WebDSL will use this property value as the base url.

sessiontimeout Sets the session timeout, specified in minutes.

javacmem javacmem=3G set javac max memory for compilation of generated Java classes

debug debug=true will show queries and Java exception stacktraces in the log.

verbose verbose=2 will show more info during compilation, mainly for developers.

fastpp fastpp=true will make the compiler write Java code faster (writing files stage), however, it also becomes less readable. (only for C-based back-end of the WebDSL compiler)

Deploy with Tomcat Manager

For the webdsl tomcatdeploy and webdsl tomcatundeploy commands to work, a user has to be configured in Tomcat (tomcat/conf/tomcat-users.xml). For example:

<tomcat-users>
  <role rolename="manager"/>
  <user username="tomcat" password="tomcat" roles="manager"/>
</tomcat-users>

The tomcat manager URL and username and password can be set in the application.ini file (defaults are listed as examples):

tomcatmanager tomcatmanager=http:\\localhost:8080\manager URL to Tomcat manager

tomcatuser tomcatuser=tomcat manager user declared in tomcat/conf/tomcat-users.xml

tomcatpassword tomcatpassword=tomcat password for that user

Action Code

This section describes the expressions and statements available in WebDSL.

Actions

Actions define targets for form submits. The body of an action contains statements See action code.

Example:

define page edituser(u : User) {
  form { 
    "Edit this user"
    label("Name:"){ input(u.name) }
    label("Group:){ input(u.group) }
    submit saveUser() {"save"}
  }
}
action saveUser() {
  u.save();
}

Inline Action

Actions may be declared inline with the submit element

Example:

define page edituser(u : User) {
  form { 
    "Edit this user"
    label("Name:"){ input(u.name) }
    label("Group:){ input(u.group) }
    submit action{u.save();} {"save"}
  }
}

Event Action Triggers

Submits for actions may be declared as properties on template elements, using the same DOM events as for Javascript, such as onclick, onblur, onkeyup (http://www.w3schools.com/jsref/dom_obj_event.asp).

Example:

define page edituser(u : User) {
  form { 
    "Edit this user"
    label("Name:"){ input(u.name) }
    label("Group:){ input(u.group) }
    image("images/save.png")[onclick := action{u.save();}]
  }
}
Page Variables

Page and template definitions can contain variables. This example displays “Dexter”:

define page cat() {
  var c := Cat { name := "Dexter" }
  output(c.name)
}

entity Cat {
  name :: String
}

These variables are necessary when constructing a page that creates a new entity instance. The instance can be created in the variable and data binding can be used for the input page elements. The next example allows new cat entity instances to be created, and the default name in the input form is “Dexter”:

define page newCat() {
  var c := Cat { name := "Dexter" }
  form{
    label("Cat's name:"){ input(c.name) }
    action("save",action{ c.save(); return showCat(c); })
  }
}

It is possible to initialize such a page/template variable with arbitrary statments using an ‘init’ action:

define page newCat() {
  var c
  init {
    c := Cat{};
    c.name := "Dexter";
  }
  form{
    label("Cat's name:"){ input(c.name) }
    action("save",action{ c.save(); return showCat(c); })
  }
}

Be aware that these type of variables (and the init blocks) are handled separately from the other elements. They do not adhere to template control flow constructs like ‘if’ and ‘for’; they are extracted from the definition. However, you can express such functionality in the ‘init’ block. For example:

error:

define page bad() {
  if(someConditionFunction()){
    var c := Cat{}
  }
  else {
    var c := Cat{ name := "Dexter" }
  }
  output(c.name)
}

ok:

define page good() {
  var c
  init{ 
    if(someConditionFunction()){
      c := Cat{}
    }
    else {
      c := Cat{ name := "Dexter" }
    }
  }
  output(c.name)
}
Pages

Pages in WebDSL can be defined using the following construct:

 define page [pagename]( [page-arguments]* ){ [page-elements]* }

There are basic output elements for structure and layout of the page, such as title and header.

Example:

define page root() {
  title { "Page title" }
  section {
    header{ "Hello world." }  
    "Greetings to you."
  }
}

Page Parameters

Pages can have parameters, and output is used for inserting data values.

Example:

define page user(u : User) {
  "The name of this user is " output(u.name)
}

Input Forms

The form element in combination with submit is used for submitting data. input elements perform automatic data binding upon submit. For more information about forms, go to the Form page.

Example:

define page editUser(u:User){
  form{
    input(u.name)
    submit action{} { "save" } 
  }
}

Templates

Pages can be made reusable by declaring them as template, and calling them from other pages or templates.

Example:

define common(){
  header{ "my page" }
}
define page root(){
  common()
}
Data Migration

Applications are constantly in motion. Bugs are fixed, new features are added, current features are improved and sometimes even an underlying architecture is changed. Being the core of most applications, data models do not escape continuous changing.

As a WebDSL application is in use, it gathers and stores data, following the structure of its data model. When a data model is updated, persistent data may be lost. Acoda prevents loss of data by automatically migrating the WebDSL database for you.

The Acoda web page

What can Acoda do

Acoda is a tool set to automatically migrate data along an evolving data model. It does not require changes to the existing development process. Input to Acoda are two versions of a WebDSL application and a database adhering to the old application. Acoda automatically detects what was changed in the data model from the old application to the new application and changes a copy of the data accordingly. Output of Acoda is a ready-to-use database dump adhering the application.

Acoda includes support for all WebDSL data model components: all types of primitive attributes, type inheritance, object relations, sets, lists, attribute cardinalities (including mandatory) and inverse relations. The changes Acoda supports include: attribute and type renaming, attribute moving, changing attribute types, resolving implicit references, wrapping attributes, introducing uniqueness and changing attribute cardinalities.

Expressions

literals
A number of literals are supported:

  • Strings: “This is a string”
  • Ints: 22
  • Float: 8.3
  • Boolean: true/false
  • List: [, , …]
  • Empty list: List()
  • Set: {, , …}
  • Empty set: Set()
  • Null: null

operators
The following operators are supported:

  • Addition (numeric types) and string concatenation: +
  • Subtraction (numeric types): -
  • Multiplication (numeric types): *
  • Division (numeric types): /
  • Modulus (integer type): %
  • Casting (casts a variable as one of another type): as (example: 8 as Float)

binary operators

  • Equality: ==
  • Inequality: !=
  • Bigger than: >
  • Bigger than or equal to: >=
  • Smaller than: <
  • Smaller than or equal to: <=
  • Instance of: is a (checks if a certain expression is of a certain runtime type)
  • Contained in collection: in (checks if a certain expression is contained in a collection)
  • and: &&
  • or: ||
  • not: !

Example:

    if(!(b is a String) && (b in [8, 5] || b + 3 = 7)) {
       // ...
    }

variables
Variables can be accessed by use of their identifiers and their properties using the . notation. Example: person.lastName

indexed access List elements can be retrieved and assigned using index access syntax:

var a := list[0]; list[2] := “test”;