Skip to main content

Authorization & Authentication

cleanup required
Some content in this article might not be up-to-date. Please take this into consideration!

This document describes the current implementation of authorization and authentication. The logic of the previous version of authentication and authorization was implemented in the control layer. Now, these implementations have been delegated to Tasks in the logic layer so that it can also be enforced in other layers. For example, with an option, the authorization can also be enforced in the logic layer.

Authentication implementations

The account component currently offers two standard implementations for authentication:

  • Username-password authentication with built-in user management.
  • OpenID Connect integration to use an external identity provider.

The model

The current model in the PR as well as the XML model allows applicationUsers, applicationProfiles and applicationUsergroups to create, edit, delete and view certain dataElements. In the new implementation, we added commands and tasks as well.

Furthermore, the option useLogicSecurity can be enabled at component level. This will ensure that logic layer security will be generated. The security in the view and control layer are now always generated in each component.

Tasks

Both authorization and authentication will run via NS tasks. Authorization is divided into data Authorization and task Authorization. Alternative implementations of these tasks can be provided as custom code by adding a file account.ns.properties to the resources folder in the logic layer of any component:

authentication.implementation=com.example.AuthenticationImpl
authorization.data.implementation=com.example.DataAuthorizationImpl
authorization.task.implementation=com.example.TaskAuthorizationImpl

AuthenticationTask

The authenticationTask runs on the input-projection of the dataElement User of the account component. This projection contains name and password. This task will fail if the authentication fails. Otherwise, this task will return an (extended) UserContext as resultClass. This UserContext is an extension to the existing BasicUserContext.

public TaskResult<UserContext> perform(ParameterContext<UserInput> targetParameter)

By default the account component uses the implementation in net.democritus.acl.AuthenticationImpl. It goes to the database for the userData from the account component and will check the (encrypted) password. This will return an extendedUserContext with profile and usergroups.

DataAuthorization task

By default the account component uses the implementation in net.democritus.acl.DataAuthorizationImpl. This implementation defaults to an "allow-all" policy and queries the DataAccess element where exclusions can be defined (see Authorization). The perform method of this task looks like this:

 public TaskResult<DataAccessRights> perform(ParameterContext<DataAccessQuery> targetParameter)

DataAccessRights is an extension to ProfileAccessRights. This distinguishes between CRUDs operations and also contains the rights to each command. UserContext is known in the task.

An improved and more secure implementation is now also available, but is not the default for backwards compatibility. For more information see configurable task implementations.

TaskAuthorization task

By default the account component uses the implementation in net.democritus.acl.TaskAuthorizationImpl. It works similar to the DataAuthorization task. Hereby we get TaskAccessRights back that show whether or not the user is allowed to perform the task.

 public TaskResult<TaskAccessRights> perform(ParameterContext<DataAccessQuery> targetParameter)

An improved and more secure implementation is now also available, but is not the default for backwards compatibility. For more information see configurable task implementations.

Configurable task implementations

A new task implementation was introduced in version 2021.3.0 of the base components. It has a more secure implementation and defaults to a deny-all policy and requires the definition of rights in the DataAccess component to enable them. They can also be reconfigured to use an allow-all policy instead, similar to the default implementation by setting the ParamTargetValue (name: defaultAccessRights, target: default) to value allowAll.

The new implementation can be enabled with the following account.ns.properties file:

authorization.data.implementation=net.democritus.acl.ConfigurableDataAuthorizationImpl
authorization.task.implementation=net.democritus.acl.ConfigurableTaskAuthorizationImpl

Control-layer

Authentication

The authentication is no longer directly validated in the control layer, but via the task in the logic layer. This task will return the userContext which is then stored in the session in the control-layer.

Authorization

The existing AccessInfoRetrieverAction is modified to request the Authorization via the DataAuthorizationTask and the TaskAuthorizationTask. This is cached in the session per task- or dataElement. Here, too, the functionalities are always validated against the accessRights, so that they are only executed if there are rights for that action.

Front-end

UI

The existing access-rights.js is modified and is extended with accessrights for commands and fields. For commands, the rights are added per command in the actions. These can then be used to keep the buttons in the frontend:

var AccessRights = require('nsx/config/access-rights');
var accessRightsController = AccessRights.getAccessRightsController();
var actionRightsRequest = accessRightsController.requestRight({
element: CarElement,
requestedRight: AccessRights.Right.ACTION,
target: input.commandName
});
...
toolbar.defineButton({
trigger: trigger,
icon: "icon-file",
label: input.commandName,
visible: ifBoth(
actionRightsRequest.approved,
isDataRefDefined(input.selection)
)
});

Old UI

The nsx-profile-access has been extended with the taskAccessRights, as can be seen in the following example: Furthermore, the jsonResult of the dataAccess will also contain the commandAccessRights.

var dataUrl = nsxApplication.getApplicationUrl() + "/account/getProfileAccessRights-json";
var taskUrl = nsxApplication.getApplicationUrl() + "/accouont/getTaskAccessRights-json"

/*
callback: function(jsonResult)
*/
function getDataAccessRightsForElement(element, callback) {
var data = {
component: element.getComponentName(),
element: element.getElementName()
};

$.get(dataUrl, data, callback);
}
/*
callback: function(jsonResult)
*/
function getTaskAccessRightsForElement(element, callback) {
var data = {
component: element.getComponentName(),
element: element.getElementName()
};

$.get(taskUrl, data, callback);
}

Logic-layer

The logic layer can also validate whether the user has rights. This is done by using the useLogicSecurity component option.

An example of the generated code in the TaskBean perform method:

DataAccessQuery dataAccessQuery = new DataAccessQuery();
dataAccessQuery.setElement(taskName);
AuthorizationManager authorizationManager = new AuthorizationManager(dataAccessQuery, userContext);
if (!authorizationManager.isTaskAuthorized()) {
return TaskResult.error(Diagnostic.error("testComponent", "CarTask", "NO_ACCESS"));
}

taskResult = mImplementation.perform(targetParameter);

An example of the generated code in the DataBean create method:

DataAccessQuery dataAccessQuery = new DataAccessQuery();
dataAccessQuery.setElement(elementName);
AuthorizationManager authorizationManager = new AuthorizationManager(dataAccessQuery,userContext);
if (!authorizationManager.isDataAuthorized(DataAccessFunctionality.CREATE)) {
return getDiagnosticHelper().createCrudsError("not authorized");
}

An example of the generated code in the DataBean performCommand method:

DataAccessQuery dataAccessQuery = new DataAccessQuery();
dataAccessQuery.setElement(elementName);
AuthorizationManager authorizationManager = new AuthorizationManager(dataAccessQuery,commandParameter.getUserContext());
if (!authorizationManager.isDataAuthorized(commandName))) {
return CommandResult.error(command, "not authorized");
}