Developer Guide
General Information
Namespace for Applications
An application's namespace should be used when naming data groups, request parameters, and session parameters. The namespace can also be, for example, a company name or a combination of a company and an application. This is where the biggest challenge lies, because with so many partners and applications, there is a real risk of naming conflicts.
The application manual should list, at a minimum, the request parameters used and their possible values if the application is to be secured in the portal using request value validators.
Data Groups
-
Maximum length of the table name: 26 characters
-
A name that is as descriptive as possible (e.g., SHARE_FEED)
-
Singular
-
English
-
Capitalization
Intrexx Data Groups
When created, Intrexx data groups are automatically named "XDATAGROUP" followed by a random key to prevent duplicate names.
File Data Groups
When you create a data field with the "File" data type, a child data group is also created automatically. This data group is named "XFILEDATAGROUP," followed by a random key. Name the file data groups using the same logic you use to name the regular data groups.
System Data Groups
System data groups should be named in the same way as Intrexx data groups. The name should always end with "SETTING."
External Data Groups
For external data groups linked to standard Intrexx data groups (e.g., DSUSER, DSOBJECT, etc.) or to Intrexx data groups within the application, renaming is not necessary.
Please make sure that the data group definition does not contain any database-specific prefixes (e.g., dbo.<data group name>), but only the data group name itself in uppercase letters.
Data Fields
It is also important to name data fields appropriately in case applications are modified or expanded later and accessed via SQL statements in Velocity or Groovy. The app's usability and its international applicability are important factors here.
Field Names
The names of Intrexx data fields consist of a type code followed by the title that was entered when the data field or the respective element was created (e.g., "STR_TITEL_6A585D30"). In this process, umlauts are converted, and the field name is truncated to 30 characters if necessary. To avoid using SQL keywords and ensure optimal readability, field names should be formulated as follows:
-
Maximum column name length: 30 characters
-
a name that is as descriptive as possible
-
Prefix consisting of the data field type code, followed by an underscore (e.g., STR_NAME, B_IS_VISIBLE, etc.)
-
English
-
Capitalization
Here is an overview of prefixes:
-
String: STR
-
Boolean: B
-
Float and Currency: FLT
-
Long text: TXT
-
Date, Time, Date and Time: DT
-
File: FILE
Primary Key
The data type of the primary key can be an integer or a GUID. A GUID has several advantages over an integer:
-
Protection Against Guessing the Record ID - Integer values can be guessed by incrementing them.
-
It is not necessary to determine the new ID when creating a new record.
-
Seamless ID — When records are deleted, gaps occur in integer values.
-
When merging or copying data that includes GUIDs, there is no need to determine IDs. GUIDs require less effort and are less prone to errors than integer values. The code required is more concise.
References
In Intrexx, the names of reference data fields are automatically prefixed with "REF_" followed by a random key. You can customize the name in the " Expert" tab using the properties of the reference data field before publishing the application for the first time. To do this, display the data fields via the main menu Edit / Show Data Fields when the data group is selected in the application structure.
Index
To optimize performance, indexes should be created for data fields that are searched particularly frequently or used for sorting. The index name must not exceed 18 characters and is structured according to the following naming convention: IX_<DG abbreviation>_<COUNTER>, e.g., IX_SHAREFEED_1.
Naming Elements
To ensure that terminology is as consistent as possible throughout the portal, there are a few rules that should be followed. As a general rule, however, you should name all elements with appropriate names whenever possible, rather than using the default names.
Page Views
Page titles are used in the browser, for example, as tooltip titles or in page path information. Therefore, the names assigned to the development cannot be logically separated from the display. However, there are a few exceptions involving application-specific view pages:
-
View page in a freely designed table: _tblPageName
-
View page in a custom mobile table: _mTblPageName
-
View pages for email delivery: _emailPageName
-
View pages for Share Feed plugins: _sharePageName
-
View pages for mobile Share Feed plugins: _mSharePageName
-
Pages to Include in PDF Generation: _pdfPageName
-
Mobile Pages: _mPageName
-
Search Results Page: _searchPageName
Groups
Since groups are often shown and hidden using JavaScript ( ), they should be given descriptive names following this pattern: _grpGroupName. The group name should be in English.
Naming Conventions
INTREXX GmbH 's Quality Assurance department performs automated testing of applications. During this process, the application structure, program code (JavaScript, Groovy, Velocity), and layout are checked for compliance with specific rules. This also includes the following terms:
|
Test Type |
Test |
Note |
|---|---|---|
|
Naming |
|
Names without a hyphen, such as eMail, email, or Email, are not permitted. However, this applies only to the labels—not to the data fields. |
|
Table length |
Compliance with the maximum length of table names |
|
|
Table column |
Length |
Compliance with the maximum length of column names |
|
Column Name Naming Convention |
LID |
The data type must be an integer |
|
Column Name Naming Convention |
FKLID |
The data type must be an integer |
|
Column Name Naming Convention |
STRID |
The data type must be a string only |
|
Column Name Naming Convention |
FKSTRID |
The data type must be a string only |
Rights
The permissions required for the application must be defined exclusively using groups. The groups must be assigned according to the following scheme and in English:
Application.<Application Name>.<Role>
In many applications, the following basic rights objects exist:
|
User Group |
Description |
|---|---|
|
Application.<Application Name>.User |
Regular user of the application |
|
Application.<Application Name>.Administrator |
Application administrator who manages settings and master data |
|
Application.<Application Name>.Manager |
Users who, for example, perform editorial tasks in an application. |
|
Application.<Application Name>.Approver |
Reviewer or approver, if the application has a corresponding process |
|
Application.<Application Name>.Reviewer |
Reviewers or individuals who provide feedback on a case |
|
Application.<Application Name>.Responsible |
Managers, such as department heads, who are granted access to their own department's data |
The "Users" user group should not be used so that access control remains flexible. In the portal, the role should be mapped to the user role in the application as needed. Specific rights objects can be defined depending on the application. The rights objects used and their functions must be documented in the application manual as shown in the table above. A filter is available to restrict the selection of users based on context; this filter can be configured with the defined permission groups. The selection of users based on their User ID is restricted using the "Is included in" option of the system value "Set and included sets" and by specifying the permission group. It is important with this filter option that inheritance is also taken into account.
Script
The program code included with the application should be written in as consistent a style as possible. Methods, function names, variables, and comments must be written as follows:
-
English
-
correct grammar and spelling
-
consistent syntax
-
proper CamelCase
JavaScript Coding Conventions
JavaScript should be avoided as much as possible. Displaying groupings and buttons conditionally using server-side Velocity code is more secure and also supports accessibility requirements.
Error Handling
When implementing your own error handling, you must use the Intrexx Notifier function to provide feedback to the user in order to maintain a consistent standard.
Notifier.status.notify("No date was specified.", "Note");
Notifier.status.error("The entered cost center does not exist.", "Error");
Velocity Coding Conventions
Security
Among other things, Velocity can be used to generate complete HTML structures. If errors occur, they can prevent an entire page—or even the entire portal—from loading. In addition, Velocity templates can be used to deliver information to which the user does not have access rights under the Intrexx framework. Therefore, Velocity templates must be thoroughly tested, and any errors must be handled.
If applications use custom Velocity files —for example, to execute their own SQL queries and thereby bypass the standard authorization structure of the Intrexx business logic—the proper use of the Velocity code should be verified. If this check is not performed, the file can simply be accessed via the browser and its contents displayed without an authorization check. This issue is similar to an SQL injection. User input is accepted without validation and passed on to API functions, which must be prevented. Your code should be enclosed within the following construct to ensure that the validation is performed on the server side:
#if($AccessController.hasPagePermission("APP_GUID", "PAGE_GUID", "access"))
## Your Code ##
#end
The GUID of the target application is specified as the first parameter, and the GUID of the target page as the second parameter. The "access" key is entered as the third parameter. The parameters should be hard-coded into the call. Passing data as request parameters makes the validation dynamic—but also makes it vulnerable again. If you want to validate access to a data group in Velocity (before performing a database operation), you can wrap the actual function with the following construct:
#if($AccessController.hasDatagroupPermission($ProcessingContext,"APP_GUID", "DATAGROUP_GUID", "read"))
## Your Code ##
#end
The first parameter is used to pass the processing context. The GUID of the target application is specified as the second parameter, and the GUID of the data group as the third parameter. One of the keys listed below must be entered as the fourth parameter:
|
Key |
Function |
|---|---|
|
create |
Create |
|
delete |
Delete |
|
delete-own |
Deleting Your Own Data |
|
read |
Read |
|
read-own |
Reading Your Own Data |
|
write |
Change |
|
write-own |
Editing Your Own Information |
The parameters should be hard-coded into the call. Passing data as request parameters makes the validation dynamic, but also makes it vulnerable to attacks.
Velocity Includes
Velocity files should always be included in the application package to ensure easy import. If there are cross-application Velocity templates, they should be stored in the portal directory `internal/system/vm/html/include`, in a separate subdirectory. All other directories are reserved for Intrexx.
Groovy Coding Conventions
Code Documentation
Code written in JavaScript, Velocity, or Groovy should be thoroughly documented. In other words, the first priority is to include sufficient, meaningful comments in the code so that its function can be understood. You can also take advantage of the option to insert JSDOC comments.
Prepared Query
The correct use of PreparedQuery to execute SQL statements in Groovy and Velocity prevents SQL injection attacks. Understanding the impact of and how to influence the parameters that are included in the SQL statement is an important prerequisite. Intrexx offers a wide range of features for using PreparedQuery. However, incorrect use can create gaps that should be avoided. In Groovy, a valid `PreparedQuery` looks like this. It is important that the values in the WHERE clause are defined using the placeholders ? and then set using set methods. The respective set methods ensure that the expected format is also inserted. In other words, if a string is passed in instead of an integer value, it will not be processed and will cause an error. The detailed error message must be suppressed in the portal for the feature to be fully effective.
def conn = g_dbConnections.systemConnection
def stmtUpdate
def stmt = g_dbQuery.prepare(conn, "SELECT LID FROM DSUSER")
def rs = stmt.executeQuery()
while (rs.next())
{
stmtUpdate = g_dbQuery.prepare(conn, "UPDATE DSUSER SET ... WHERE LID = ?'")
stmtUpdate.setInt(1, rs.getIntValue(1))
stmtUpdate.executeUpdate()
stmtUpdate.close()
}
rs.close()
stmt.close()
The following construct is incorrectly defined and poses a high risk.
def conn = g_dbConnections.systemConnection
def l_UserId = g_record["GUID"].value
def stmt = g_dbQuery.prepare(conn, "SELECT DTBIRTH FROM DSUSER WHERE LID = '${l_UserId}'")
def rs = stmt.executeQuery()
while (rs.next())
{
rs.getDateValue(1)
}
rs.close()
stmt.close()
A hacker could manipulate the value in the data field and inject a string containing the WHERE clause extension instead of the expected LID.
Expected:
WHERE LID = 10
Manipulated:
WHERE LID = 10 OR LID > 0
With this change, the hacker gains access to all entries in the user database. If the request values are also checked using validators, strings in request values that are supposed to return integer values, for example, won't even make it far enough to be inserted into the statement. Another very complex design is shown below. The SQL statement is formed by concatenating a string and a variable. Here, too, the variable—which is also passed via a request parameter—can be exploited to extend the WHERE clause.
def l_request = g_request.get("rq_myparameter")
def l_sql = "SELECT DTBIRTH FROM DSUSER WHERE LID = "l_sql += l_request
def stmt = g_dbQuery.prepare(conn, l_sql)
def rs = stmt.executeQuery()
...
The same conditions apply in the Velocity environment. Here, too, the PreparedQuery must be used correctly:
#set($UserId = $DC.getValueHolder("GUID").getValue())
#set($stmt = $PreparedQuery.prepare($DbConnection,
"SELECT DTBIRTH FROM DSUSER WHERE LID = ?"))
$stmt.setInt(1, $UserId)
#set($rs = $stmt.executeQuery())
#foreach($element in $rs)
$element.getDateValue(1)
#end
$rs.close()
$statement.close()
Another complex construct is the definition of a query in which the data field names are inserted dynamically. This is especially true when the field names are determined based on data from the request or other external sources. This applies to Groovy and Velocity.
def ergebnis = g_dbQuery.executeAndGetScalarIntValue(conn,
"SELECT count(*) FROM DATAGROUP WHERE LID = ? AND $FIELDNAME1 IS NOT NULL AND $FIELDNAME <> ''",0)
A solution like this requires the implementation of many security measures. The ideal solution is to avoid such constructs. The following safety measures are recommended:
-
The client sends any unique key values for the individual fields to the server. The key values can be, for example, the GUIDs of data field names or query string parameters.
-
The transmitted values are checked in advance using appropriate validators (e.g., whitelist). If no match is found, no server-side processing takes place.
-
The server checks whether the logged-in user has the necessary permissions to execute the following code (typically, read permissions for the data group).
-
The server looks up the allowed field GUIDs in a list. If it encounters an unassigned parameter during this process, it either throws an exception or ignores the parameter—depending on the requirements.
-
The field GUIDs are used to determine the corresponding column names in the database and to construct a prepared statement.
-
The current values are passed to the prepared statement.
-
The database query is being executed
Multilingual Applications
You can find all the information on this topic here.
Clonable Applications
In order to duplicate an application by copying it, it is essential that Groovy and Velocity scripts be defined correctly. This applies primarily to the data groups and the SQL statements. When duplicating an application, Intrexx reassigns all GUIDs and also changes the GUIDs in the scripts (Groovy, Velocity, JavaScript) so that everything aligns again. For this reason, data group definitions must always be specified using the data group's GUID:
def l_strIsoLanguage = it
def l_intLanguageDetect = g_dbQuery.executeAndGetScalarValue(conn,
"SELECT COUNT(*) FROM DATAGROUP('98C0EC3CC539925C8B7644F4AB726BE2F38038F1')
WHERE LANG = ?", 0) {
setString(1, l_strIsoLanguage)
}
#set($stmtFloors = $PreparedQuery.prepare($DbConnection,
"SELECT T0.STRID, T1.STRNAME, T1.STRSHORTNAME FROM
DATAGROUP('B1156946E0CF3215576D4595A757A3FD0BB31C22') T0 LEFT OUTER JOIN
DATAGROUP('1037FA883B005B35FE5D4B8A10645B9FB2B04933') T1 ON ((T0.REF_PROPERTY
= T1.PROPID) AND (T1.LANG = ?)) WHERE T0.STRPARENTID = ? AND T0.REF_CLASS = ?
ORDER BY T1.LSORT"))
$stmtFloors.setString(1, $lang)
$stmtFloors.setString(2, $Builiding)
$stmtFloors.setString(3, "LEVEL")
#set($rsFloors = $stmtFloors.executeQuery())
GUIDs should also be used to access data fields on application pages in the Velocity context. To do this, set the "page.requiredDataFields.mode" key to "all" in the settings for the respective page. This is the fastest way to expose all data fields in the data group using GUIDs. It also serves as the basis for accessing the Velocity templates discussed in the previous sections.
In the Velocity context, the stored data can then be accessed using the following methods:
$DC.getValueHolder("GUID_DATA_FIELD").getValue()
## When page is used in a free table
$drRecord.getValueHolder("GUID_DATA_FIELD").getValue()
The advantage of this method is that the information does not have to be physically placed on the page as a viewport (and potentially hidden).
Portable Applications (Databases)
Since Intrexx can be used with different databases, SQL statements should be defined as neutrally as possible—that is, without any database-specific functions. The ANSI SQL standard must be followed. If there is no neutral alternative for an SQL statement, a database switch must be defined for that SQL statement that uses the appropriate statement for each database.
Switch for Groovy:
def conn = g_dbConnections.systemConnection
switch (conn.descriptor.databaseType)
{
case "Db2":
// DB2
break
case "Derby":
// Derby/Java DB
break
case "Firebird":
// Firebird
break
case "HSQLDB":
// HSQLDB
break
case "Ingres":
// Ingres
break
case "Oracle8":
// Oracle 8
break
case "Oracle9":
// Oracle 9
break
case "Oracle10":
// Oracle 10
break
case "Oracle11":
// Oracle 11
break
case "Oracle12":
// Oracle 12
break
case "PostgreSQL":
// PostgreSQL
break
case "MaxDB":
// MaxDB
break
case "MsSqlServer":
// Microsoft SQL Server
break
case "Standard":
// unspecified
break
default:
assert false : "Unexpected database type."
break
}
Switches for Velocity:
#set($DbName =
$DbUtil.getConnection("IxSysDb").getDescriptor().getDatabaseType())
#if($DbName == "MsSqlServer")
#set($sql = " SELECT TOP 10 * FROM MyTable ORDER BY LID DESC")
#elseif($DbName == "PostgreSQL")
#set($sql = "SELECT * FROM MyTable ORDER BY LID DESC LIMIT 10")
#elseif($DbName == " Oracle9" || $DbName == " Oracle8")
#set($sql = " SELECT * FROM (SELECT * FROM MyTable ORDER BY LID DESC)
WHERE rownum <= 10")
#else
$Debug.info("Unexpected database type")
#end
Processes
When working with processes, keep the following in mind: Whenever possible, timer actions on data groups should use a filter to limit the number of results in order to minimize the number of database operations. By default, the "Log only warnings and errors" setting should be selected in the process properties. g_log.info() statements added for debugging purposes can remain in the code. They are ignored during normal operation and keep the log files small.
