Introduction

I was recently confronted with some seemingly simple requirements in OpenText Content Server development. In one case a process was required to search and replace all instances of a category attribute value (a common requirement when updating lookup and popup type attributes). It sounded simple with a LiveReport and WebReport, but grew in complexity once we had to consider multivalued attributes within multivalued sets (try building a generic solution if you’re not convinced of its complexity). I suggested a simple one-off OScript module (based on the AttrData extensions in RHCore), but it was not an option since the target system was highly controlled and installing a module was nearly impossible.

Around the same time a user posted a question to the OpenText Knowledge Center forums asking if it were possible to schedule the generation of a System Report. This can’t be done with standard tools, and so I suggested the development of a small custom module. I received no reply, which I assume meant it was also not an option.

These types of requirements are sometimes just a few lines of code. That’s the easy part, but the logistics of getting a module developed and installed can often be much more difficult or impossible.

Developers will often use tools such as LiveReports or WebReports to get around this. These tools allow complex reports to be written without having to build and install an additional module. This is great and serves a purpose, but is limiting since neither LiveReports or WebReports are scripting languages (albeit WebReports has a few “action” tags).

Writing OScript is sometimes the best or only solution to a problem, but there lacks a way to write OScript without having to build and install a module. But what if there was a way to write OScript from the web interface in the same way LiveReports allows you to write SQL? This would open many possibilities for creating small scripts and applications without having to deal with the logistics of installing a module. I pondered the idea for a few years and hesitated due to concerns with system integrity and security. However, I came to realise it wasn’t a problem as long as one adopted a good programming style, had a solid foundation to build on, and trusted Content Server permissions. I prototyped the idea, built a few small applications, and was surprised by how simple and powerful it was. Why hadn’t I done this before? I formalised the prototype and turned it into a subtype called ScriptNode, which is now a part of RHCore.

What is ScriptNode?

ScriptNode is a Content Server node type that allows a privileged user to write and execute OScript directly from the web interface. ScriptNode isn’t a front-end for module development; rather, it allows a user to write scripts in the browser that can be executed on demand or on a schedule. Think of it like a LiveReport, but instead of writing SQL you write OScript. ScriptNode is backed by the RHCore API, which makes it a simple and powerful solution for creating small scripts and applications without having to develop, install, or maintain a module. The only requirement is RHCore.

A ScriptNode can be added to a folder or other container through the “Add Item” menu (as long as the user has the permissions and privileges) and has a simple editor. For example, a ScriptNode to display “Hello World!” back to the user could look as follows:

The text area is for the script and is analogous to the script window in Builder. Executing the example outputs the following:

A ScriptNode script is wrapped in a temporary object at runtime, which provides a number of convenient methods via the this context. For example, this.echo() (or just .echo() as in the example) is analogous to the standard echo() function, but is used to write the output to the browser (instead of the debug window or logs). Other methods include shortcuts for fetching an RHNode, RHUser, program context, request, etc.

Input parameters are defined above the text area and is similar to how parameters are defined in a LiveReport. The “Prompt” field sets the display label and the “Field Type” defines the input widget and data type.

Running a ScriptNode with input parameters first prompts the user for the values before calling the script. Arguments can be accessed in the script by matching the function declaration to the parameters or by using the .args() function. The .args() function returns the arguments as a list, but also accepts an integer to return the argument at a specific index (e.g., .args(3) for the third argument).

Let’s look at another example.

Example: E-Mail a Group

Say you require a tool to send an e-mail to the members of an arbitrary group. This can be built with ScriptNode with a few lines of code.

The first step is to define the input fields:

a “To” field of type “KUAFGroup” (an autosuggest field for groups) to input the recipient group;

a “Subject” field of type “String” to input the e-mail subject; and

a “Body” field of type “Text” to input the body of the e-mail.

The next step is to write the OScript to send the e-mail. This could be done with the $Kernel.SMTPClient library, but for the example we’ll use the EMailer class I introduced in Part XIII: Sending E-Mail from OpenText Content Server. Putting it together looks as follows:

Running the ScriptNode prompts the user with the following form, which can be filled in and submitted to send the e-mail to the group members:

That’s it! Let’s look at a few more examples.

Other Examples

I’ve used ScriptNode to create a number of other small tools and applications. These are conceptually similar to the previous example and include:

reporting on the effective permissions of a user on a node;

monitoring a file system directory for documents and adding them to Content Server when detected;

transferring ownership of all nodes belonging to a user to another user (useful when a user account is to be deleted);

searching and replacing category attribute values;

generating a System Report and adding it as a document to Content Server (could also be e-mailed);

analysing a workflow map to determine where certain actions are taking place;

automatic sending of an e-mail to members of a testing team whenever a module under development is updated on the server;

monitoring the logs/ directory for trace files and e-mailing them to the administrator when detected; and

reporting on installed patches by fetching the list of files in the patch/ directory, extracting the header from each file, and outputting the results.

All of these examples are just a few lines of code and can be added to a system without having to build or install a module each time. Again, the only requirement is RHCore.

Other Features

Run on a Schedule

A ScriptNode can be scheduled to run once at a future date or regularly on a schedule. This is configured on the “Scheduler” tab and looks as follows:

Scheduled ScriptNodes are handled by the standard Content Server agents and run in the context of the Admin user.

Workflow Generic Callback

ScriptNode also integrates with the Workflow Generic Callback subsystem, which means you can use ScriptNode to write Workflow Event Scripts directly from the web interface.

WebReports and Other Node Types

A ScriptNode can also call a WebReport and vice-versa (via a drop-in sub-tag). This creates some interesting possibilities to add custom logic to a WebReport, or use a WebReport to template the output of a ScriptNode.

Of course, a ScriptNode can also call a LiveReport, a Simplate, or any other ScriptNode.

Event Callbacks

This is still a work in progress, but I plan to allow ScriptNodes to respond to node and user events (e.g., run a ScriptNode when a document is added to a specific folder). This could be used to launch a workflow, notify a user with an e-mail, or anything else.

What about security?

You might be thinking this is a security risk. But if you think about it, it’s no different than the security risk associated with a LiveReport. The permission to create, edit, and execute a LiveReport depends on standard permissions and its Object Privilege (via the Administer Object and Usage Privileges admin settings). These must be enforced to prevent an unscrupulous user from writing and executing a LiveReport to augment their permissions or do something crazy like drop tables. The same argument can be made for ScriptNode.

ScriptNode also takes it a step further by restricting who can edit scripts. Object Privileges normally just restricts the creation of nodes, but with ScriptNode it also restricts the editing. I think this is logical due to the sensitive nature of the object type.

Finally, ScriptNodes should be developed with the same diligence used to write a module. They should be developed, tested, and vetted in a development environment before moving to production. The reason for this should be obvious: An obscure typo or bug could lead to something destructive (e.g., losing data, creating an infinite loop, etc.). Also, a development environment allows a developer to debug the ScriptNode using Builder or CSIDE.

Wrapping Up

I’m using ScriptNode in a few projects and enjoy the ease at which I can develop a small application or one-off solution with little constraint. This has been especially useful in environments where installing a module is highly controlled and difficult.

Please leave a comment if you have any questions or thoughts of where you might find this useful. Finally, if you’re interested in seeing a demo then please get in touch!

Addendum (18 March 2015)

Some readers have commented on security concerns with ScriptNode. While I don’t necessarily agree with these concerns (see the comments below), it should be noted that ScriptNode is an optional part of RHCore and is disabled by default. It must be manually enabled after RHCore is installed before it can be used.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

Once again you amaze the whole world of oScript. This was my idea to retire.I had always seen Documentum workflow allowing java code to put in screen where it would execute in that workflow context and thought I should write one of these that could compile oScript on the fly.Awesome once again

Hi Appu: The workflow component was a little tricky since the integration required the workflow scripts to be dynamic generated and populated at runtime. However, how do you keep everything in sync among all the threads (i.e., change a ScriptNode setting in one thread and it’s immediately updated in all others)? Fortunately, I could (mis)use the $LLIAPI.ThreadCache object to force the rebuild of these callbacks whenever something was changed. The added benefit is that workflow callbacks can now be created and edited without having to restart Content Server. Thanks for your comment!

Hi Karim, there are no restrictions. For this reason you can only create and edit a ScriptNode if you’re an admin user or granted the privilege via the admin “Object Privileges” page. However, anyone can run a ScriptNode as long as they have “See Contents” permission. Thanks for your comment!

Hi JR: What you can and cannot do with a LiveReport versus ScriptNode isn’t the point. I’m not comparing the severity of what could be done if one or the other were compromised. Both would be really bad. My argument is that the same mechanisms used to secure a LiveReport (i.e., control who can create, edit, and execute) is also applicable to ScriptNode. If you can’t trust Content Server permissions and privileges to do that then you have a much bigger problem.

The use of ScriptNode depends on trusting those who develop with it, and you grant this trust through object privileges. It’s no different than trusting a module developer, a user with administrative rights, someone who can write LiveReports, the DBA, the IT department, a third-party consultant, or anyone else with physical access to the server. Each has some type of access and must be trusted not to abuse it. It’s no different for ScriptNodes or LiveReports.

Hi DT: I agree that WebReports pre-processing doesn’t secure much (see my other comment in response to Evan). But in defence of WebReports, an admin user must approve a WebReport containing an [LL_WEBREPORT_STARTSCRIPT /] … [LL_WEBREPORT_ENDSCRIPT /] block before it can be executed. This shows up on the Specific tab of the WebReport on a checkbox field called “Oscript Scripting Enabled” (which gets disabled on each save unless you’re an admin user). This extra check prevents a non-Admin user from writing and executing arbitrary scripts regardless of their privileges or node permissions. If you have written all your WebReports with an admin user then you might not have seen this setting before.

What matters is not whether you can trust the privileges mechanism, but if it’s worth creating this potential security breach.

I disagree. Privileges do matter. Otherwise your argument would also imply that sensitive documents shouldn’t be uploaded to Content Server. Content Server must do its job at securing its content and ScriptNode doesn’t change this.

If you could already exploit the privileges mechanism to run arbitrary database calls with a LiveReport then you already have everything you need to gain unauthorised access or corrupt the system. A ScriptNode may provide additional ways to hack a system, but only after the same mechanism used to secure a LiveReport (or any other content for that matter) is breached.

If you or any other reader have thoughts on how ScriptNode could be used to exploit a system I’d be happy to hear about it.

Nice job. I’ve often thought of writing such an object because the need does arise fairly frequently when it would be nice to be able to run code quickly without loading up builder. The security issue is real, but I think you are probably right that if you can trust permissions, this is about as dangerous as LiveReports. However, this would become an attack vector if a SQL injection attack were possible somewhere else in content server.

I can see this being quite cool if you could limit the input to a subset of oscript that might prevent access to certain globals and prevent file access and socket access (or other builtins).

Hi Evan: I find myself using ScriptNode often for that purpose. I was recently required to move some content from one system to another, and did it with an xml export and import using ScriptNode with just a few lines of code. I realized later I could schedule these ScriptNodes to make a quick and dirty “Dropbox” to synchronize content between instances. I’m finding more uses all the time.

You make a good point that a SQL injection attack could open the doors to unregulated access to ScriptNode. That’s the thing about security: You’re as strong as your weakest link. You break down one wall and it opens up a whole range of other exploits.

I’m not in favour of restricting what packages can and can’t be accessed. A black list will never ensure you covered all execution paths (e.g., off the top of my head I can think of four different ways to gain file access), and a white list would severely restrict what you can do. I see ScriptNode as an administration and development tool that must be treated with the same care and respect as a LiveReport or a module under development.

Yeah, you’re right — I think the problem of selectively restricting access to code would be quite challenging to do well. I was thinking in terms of a recent project I’ve completed – I recently wrote a full OScript lexer/parser, which would be useful in a case like this. My use case was much different — I used it to write a profiler and code coverage tool, which works really well and is pretty awesome for finding performance issues in large projects.

The profiler instruments the code to record entries and exits from each function within each script in an ospace. It keeps track of the full function stack so after you run the instrumented code, you can get timings for each function, and since the stack is known at any given time, it’s possible to calculate the time that a function spent calling other functions. In the profiler, I display all the typical timing and hit stats and I also use the stack data to render a flame graph of the oscript execution stack with timings.

The code coverage tool works essentially the same way, except it finds all the basic blocks in the code, so it can record entries and exits from each block. This way, it can efficiently record function timings as well as line hit-counts. From the code coverage data, I create a code coverage report which contains all the source code in the ospace, annotated with the function timings, line hits, missed lines, etc.. It’s quite fun to browse around your code that way.

To create code blocks or other preformatted text, indent by four spaces:

This will be displayed in a monospaced font. The first four
spaces will be stripped off, but all other whitespace
will be preserved.
Markdown is turned off in code blocks:
[This is not a link](http://example.com)