Security
This document is referring to a past Scout release. Please click here for the recent version. |
Default HTTP Response Headers
All Scout HTTP servlets delegate to a central authority to append HTTP response headers. This is the bean HttpServletControl
.
It enables developers to control which headers that should be added to the HTTP response for each servlet and request.
The next sections describe the headers that are added to any response by default. Beside these also the following headers may be of interest for an end user application (consider adding them to your application if possible):
Please note that not all headers are supported in all user agents! |
X-Frame-Options
The X-Frame-Options HTTP response header [1] can be used to indicate whether a user agent should be allowed to render a page in a <frame>
, <iframe>
or <object>
.
Sites can use this to avoid clickjacking [2] attacks, by ensuring that their content is not embedded into other sites.
The X-Frame-Options header is described in RFC 7034 [3].
In Scout this header is set to SAMEORIGIN
which allows the page to be displayed in a frame on the same origin (scheme, host and port) as the page itself only.
X-XSS-Protection
This header is no longer interpreted by modern browser engines and is considered insecure.
Scout does no longer set this header in HttpServletControl
.
Content Security Policy
Content Security Policy is an HTTP response header that helps you reduce XSS risks on modern user agents by declaring what dynamic resources are allowed to load [4]. The CSP header is described in Level 1 and Level 2. There is also a working draft for a Level 3.
Scout makes use of Level 1 (and one directive from Level 2) and sets by default the following settings:
-
JavaScript [5]: Only accepts JavaScript resources from the same origin (same scheme, host and port). Inline JavaScript is allowed and unsafe dynamic code evaluation (like
eval(string)
,setTimeout(string)
,setInterval(string)
,new Function(string)
) is allowed as well. -
Stylesheets (CSS) [6]: Only accepts Stylesheet resources from the same origin (same scheme, host and port). Inline style attributes are allowed.
-
Frames [7]: All sources are allowed because the iframes created by the Scout BrowserField run in the sandbox mode and therefore handle the security policy on their own.
-
All other types (Image,
WebSocket
[8],EventSource
[9], AJAX calls [10], fonts,<object>
[11],<embed>
[12],<applet>
[13],<audio>
[14] and<video>
[15]) only allow resources from the same origin (same scheme, host and port).
If a resource is blocked because it violates the CSP a report is created and logged on server side using level warning
. This is done in the class ContentSecurityPolicyReportHandler
.
This enables admins to monitor the application and to react if a CSP violation is detected.
Session Cookie (JSESSIONID Cookie) Configuration Validation
The UiServlet checks if the session cookie is configured safely. The validation is only performed on first access to the UiServlet
. There is no automatic validation on the backend server side or on any custom servlets!
If the validation fails, a corresponding error message is logged to the server and an exception is thrown making the UiServlet
inaccessible. Because of security reasons the exception shown to the user includes no details about the error. These can only be seen on the server side log.
HttpOnly
First the existence of the HttpOnly
flag is checked. The servlet container will then add this flag to the Set-Cookie
HTTP response header. If the user agent supports this flag, the cookie cannot be accessed through a client side script. As a result even if a cross-site scripting (XSS) flaw exists and a user accidentally accesses a link that exploits this flaw, the user agent will not reveal the cookie to a third party.
For a list of user agents supporting this feature please refer to OWASP.
This flag is enabled by default via scout.app.sessionCookieConfigHttpOnly
.
Secure
Second the existence of the Secure
flag is checked. The servlet container will then add this flag to the Set-Cookie
HTTP response header. The purpose of the secure flag is to prevent cookies from being observed by unauthorized parties due to the transmission of a cookie in clear text. Therefore, setting this flag will prevent the user agent from transmitting the session id over an unencrypted channel.
This flag is enabled by default for non-development builds via scout.app.sessionCookieConfigSecure
.
This of course only makes sense if the application is exposed to the end user using an encrypted channel like HTTPS (which is strongly recommended).
Secure Output
This chapter describes how HTML Output can be handled in a secure way.
Scout applications often display potentially dangerous data, e.g. user input or data from other systems. Encoding this input in such a way, that it can not be executed, prevents security vulnerabilities like cross-site scripting.
Encoding by Default
By default, all input in the Scout model is encoded. Examples are values/labels in value fields, cells in tables, message in message box. The reason behind this default choice is that developers do not have to think about output encoding in the standard case and are therefore less likely to forget output encoding and introduce a security vulnerability.
Example: In the following label field, the HTML <b> tag is encoded as <b>bold text</b>
:
public class LabelField extends AbstractLabelField { @Override protected void execInitField() { setValue("...<b>Bold text</b>..."); }
Html Enabled
Sometimes developers may want to use HTML in the Scout model.
Examples are
-
Simple styling of dynamic content, such as addresses or texts in message boxes
-
Text containing application-internal or external links
-
Html or XML content received from other systems, such as e-mails or html pages
Html input should only partially be encoded or not at all.
To disable the encoding of the whole value, the property HtmlEnabled
can be used:
public class NoEncodingLabelField extends AbstractLabelField { @Override protected boolean getConfiguredHtmlEnabled() { return true; }
@Override protected void execInitField() { setValue("...<b>Bold text</b>..."); }
There are several ways to implement the use cases above. Some typical implementations are described in the following sections.
CSS Class and Other Model Properties
Often using HTML in value fields or table cells is not necessary for styling. Very basic styling can be done for example by setting the CSS class.
HTML Builder
For creating simple HTML files or fragments with encoded user input, the class org.eclipse.scout.rt.platform.html.HTML
can be used. It is also easily possible to create application internal and external link with this approach.
Styling in the UI-Layer
For more complex HTML, using IBeanField
in the scout model and implementing the styling in the UI-Layer is often the preferred way.
Links are possible as well.
Manual Encoding
It is also possible to encode any String manually using StringUtility.htmlEncode(String)
. org.eclipse.scout.rt.platform.html.HTML
uses this method internally for encoding.
However, using HTML
is recommended, where possible, because it is more concise and leads to less errors.
Using a White-List Filter
If HTML or XML from external sources or more complex HTML are used in the Scout model, using a white-list filter might be the best way to avoid security bugs. Libraries, such as JSoup provide such a white-list filter. Scout currently does not include any services or utilities for using white-list filters, because the configuration and usage is very use-case-specific and would therefore not add much benefit.
Authorization (Granting)
Scout uses the java.security
API principles to grant access to a specific resource.
Each user has a set of granted java.security.Permission
instances.
This set is a java.security.PermissionCollection
.
A call to PermissionCollection.implies(Permission p)
does the access check.
The argument p
in this call is a new permission instance for which we want to do the access check and which is compared against the granted permissions.
Usually, the permission collection implementation iterates through all granted permissions and calls on each Permission.implies(Permission p)
until one call returns true
.
Scout adds some concepts and helpers to this API:
IPermission
-
Unlike other permissions, a permission implementing this interface can only be implied by another
IPermission
with the same name. A permission used together with scouts security API does not have to implement theIPermission
interface, but it is recommended. PermissionLevel
-
An
IPermission
, which is part of anIPermissionCollection
has always a granted access level assigned (IPermission.getLevel()
). If the granted level isPermissionLevel.NONE
, any access checks will fail. IAccessControlService
-
This service is responsible to provide and manage a users set of granted permissions. A scout application usually extends
AbstractAccessControlService
and implements#execLoadPermissions
. ACCESS
-
Provides a set of convenience methods to check access.
Let us assume you require a permission to allow a user to access companies.
public class ReadCompanyPermission extends AbstractPermission {
private static final long serialVersionUID = 1L;
public ReadCompanyPermission() {
super("scoutdoc.ReadCompany");
}
}
To check access one can use ACCESS
.
if (ACCESS.check(new ReadCompanyPermission())) { (1)
throw new AccessForbiddenException(TEXTS.get("YouAreNotAllowedToReadThisData"));
}
ACCESS.checkAndThrow(new ReadCompanyPermission()); (2)
1 | Checks permission against granted permissions of current user. |
2 | Checks permission and if this check fails, throw an AccessForbiddenException with a default message. |
We can define a default access check failed message for a permission.
public class CreateCompanyPermission extends AbstractPermission {
private static final long serialVersionUID = 1L;
public CreateCompanyPermission() {
super("scoutdoc.CreateCompany");
}
@Override
public String getAccessCheckFailedMessage() {
return TEXTS.get("YouAreNotAllowedToRegisterThisData");
}
}
ACCESS
allows to check multiple permissions at once.
ACCESS.checkAllAndThrow(new ReadCompanyPermission(), new CreateCompanyPermission());
ACCESS.checkAnyAndThrow(new ReadCompanyPermission(), new CreateCompanyPermission());
We have seen some simple permission checks.
Now let us assume, that some users may modify a company only if they have registered the company by themselves.
For this use case we introduce a new permission level ScoutdocPermissionLevels.OWN
.
This is the permission level which is granted for those users.
public final class ScoutdocPermissionLevels {
private ScoutdocPermissionLevels() {
}
public static final int LEVEL_NONE = PermissionLevel.LEVEL_NONE;
public static final int LEVEL_OWN = 10;
public static final int LEVEL_ALL = PermissionLevel.LEVEL_ALL;
public static final PermissionLevel NONE = PermissionLevel.NONE;
public static final PermissionLevel OWN =
PermissionLevel.register(LEVEL_OWN, "OWN", true, () -> TEXTS.get("Own"));
public static final PermissionLevel ALL = PermissionLevel.ALL;
public static void init() {
// ensures all static initializers have been called
}
}
In order to check access for this new level we have to override AbstractPermission#evalPermission
.
public class UpdateCompanyPermission extends AbstractPermission {
private static final long serialVersionUID = 1L;
private final UUID m_companyId;
public UpdateCompanyPermission() {
this(null);
}
public UpdateCompanyPermission(UUID companyId) {
super("scoutdoc.UpdateCompany");
m_companyId = companyId;
}
public UUID getCompanyId() {
return m_companyId;
}
@Override
protected boolean evalPermission(IPermission p) {
// Precondition: p.getClass() == getClass() && getName().equals(p.getName()) &&
// getLevel() != PermissionLevel.NONE
if (ScoutdocPermissionLevels.OWN == getLevel()) {
UUID companyId = ((UpdateCompanyPermission) p).getCompanyId();
return BEANS.get(ICompanyService.class).isOwnCompany(companyId);
}
return true; // ScoutdocPermissionLevels.ALL == getLevel()
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((m_companyId == null) ? 0 : m_companyId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
UpdateCompanyPermission other = (UpdateCompanyPermission) obj;
if (m_companyId == null) {
if (other.m_companyId != null) {
return false;
}
}
else if (!m_companyId.equals(other.m_companyId)) {
return false;
}
return true;
}
}
ACCESS.checkAndThrow(new UpdateCompanyPermission(companyId));
If such a service call would be expensive, one may cache the result of such a permission check.
You have to implement this by yourself.
A recommended solution is to create a wrapper around IPermissionCollection
and this wrapper caches calls to IPermissionCollection#implies
if required.
Scout JS
The access
utility provides the possibility and convenience of checking permissions in Scout JS code.
All necessary information the Scout JS code needs is made available by the PermissionResource
in the org.eclipse.scout.rt.api
-module.
To enable the feature this REST resource needs to be added to the server and the access
utility needs to be bootstrapped in the entry file.
<dependency>
<groupId>org.eclipse.scout.rt</groupId>
<artifactId>org.eclipse.scout.rt.api</artifactId>
</dependency>
access
utility in the entry file myapp.js
new App().init({
bootstrap: {
permissionsUrl: 'api/permissions'
}
});
If the visibility of a field depends on a permission the access
utility can be used to evaluate this permission.
The method quickCheck
returns a boolean
while check
returns a JQuery.Promise<boolean>
field.setVisible(access.quickCheck('SomePermission'));
access.check('SomePermission').then(granted => field.setVisible(granted);
If one has created IOwnPermission
s with a special implies
-logic and wants to make them available in Scout JS a couple of things need to be added:
-
OwnPermissionDo
, data object used to transfer additional properties from the server to the Scout JS code -
ToOwnPermissionDoFunction
,IToDoFunction
transformingIOwnPermission
into the data object -
OwnPermission
, Scout JS pendant forIOwnPermission
used by theaccess
utility
OwnPermissionDo
containing the ToOwnPermissionDoFunction
@TypeName("myapp.OwnPermission")
public class OwnPermissionDo extends PermissionDo {
protected static final String OWN_PERMISSION_OBJECT_TYPE = "myapp.OwnPermission";
public DoValue<String> foo() {
return doValue("foo");
}
public DoValue<String> bar() {
return doValue("bar");
}
/* **************************************************************************
* CUSTOM CONVENIENCE TO DO FUNCTION
* *************************************************************************/
@Order(4000)
public static class ToOwnPermissionDoFunction extends AbstractToPermissionDoFunction<IOwnPermission, OwnPermissionDo> {
@Override
public void apply(IOwnPermission ownPermission, OwnPermissionDo ownPermissionDo) {
BEANS.get(ToPermissionDoFunction.class).apply(ownPermission, ownPermissionDo);
ownPermissionDo
.withObjectType(OWN_PERMISSION_OBJECT_TYPE) // important to create the correct Scout JS pendant
.withFoo(ownPermission.getFoo())
.withBar(ownPermission.getBar());
}
}
/* **************************************************************************
* GENERATED CONVENIENCE METHODS
* *************************************************************************/
// ...
}
OwnPermission
override matches(permission: Permission): boolean {
if (!super.matches(permission)) {
return false;
}
// check if permission is OwnPermission and foo matches
if (!(permission instanceof OwnPermission)) {
return false;
}
return this.foo === permission.foo;
}
protected override _evalPermissionQuick(permission: Permission): boolean {
// check if bar matches
return super._evalPermissionQuick(permission) && this.bar === permission.bar;
}
protected override _evalPermission(permission: Permission): JQuery.Promise<boolean> {
// call server to evaluate foo and bar
return ajax.getJson(`api/permissions/checkFooBar/${permission.foo}/${permission.bar}`)
.catch((error: AjaxError) => {
// handle error and return false
return false;
});
}
The PermissionResource
will automatically collect the granted IOwnPermission
s of a user (see Authorization (Granting)).
These IOwnPermission
s will be transformed by the ToOwnPermissionDoFunction
and send to the browser where OwnPermission
instances are created.
Each access check will now automatically consider the matches
-, implies
- and evaluate
-logic of OwnPermission
.
Login Form
Scout provides support for form based login.
To activate the form based login, a login.html
needs to be provided and the FormBasedAccessController
along with a ICredentialVerifier
needs to be added to the AuthFilter
.
An example setup can be found in the archetype.
Two-factor Authentication (2FA)
The form based login also supports two-factor-authentication.
If the registered ICredentialVerifier
requests a second factor by returning AUTH_2FA_REQUIRED
, the FormBasedAccessController
instructs the UI to show an input field so the user can enter the token.
To verify this token a second-factor verifier ICredentialVerifier
needs to be registered.