Found Pages
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.
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()}
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* }
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();}]
}
}
This section describes the expressions and statements available in WebDSL.
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 Sqlite
db Set ‘db=sqlite’ to enable Sqlite instead of the default MySQL.
dbfile Sqlite 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 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&characterEncoding=UTF-8&useUnicode=true&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.
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
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 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()
}
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.
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.
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”;
