-
Notifications
You must be signed in to change notification settings - Fork 4
cdo.security
Model and helper classes for protecting access to repository objects at the application level.
Root application classes shall implement this interface. E.g. in Examples SystemOfRecords
implements ProtectionDomain<LoginPasswordCredentials>
. Protection domain contains actions, but doesn't have to directly contain users and groups. There might be different
types of users stored in different parts of the model. E.g. the Examples there are two types of users - Guest and Customer. Groups can be
organized in application-specific manner, e.g. nested or contained in model elements other than the element implementing protection domain
(for example Organization might be the root element/protection domain and groups might be contained in Organization Units).
- Operations
-
authenticate()
- Authenticates credentials and returns an instance of user or null if authentication is successful. -
clearPermissions()
- Removes permissions associated with a particular object from the model. Call this method to avoid dangling references. -
getAllUsers()
- Returns all users in the protection domain.
-
- References
-
unauthenticatedPrincipal
- All security checks are performed against a principal. If a system user hasn't authenticated yet then unauthenticatedPrincipal is used for security checks. In Examples Guest is unauthenticated principal - it has its home page with sign-in and sign-up functionality and a list of bank products. -
superUsersGroup
- Members of this group have all permissions in the system. -
everyoneGroup
- Every authenticated principal is an implicit member of this group.
-
Principal is an entity which can be authorized by the system to perform actions in the system, in particular on repository elements.
User is a principal which can be authenticated by the system using credentials.
Groups allow to assign permissions to group members in one operation.
Action can be granted (allowed) or denied to a principal through a permission.
Actions can be scoped to particular classes (and their subclasses) and namespaces
with targetClass
and targetNamespaceURI
attributes. For EObjects targetClass is class name and targetNamespaceURI is the targetNamespaceURI of the containing package.
For other objects targetClass is the part of the class name after the last dot and targetNamespaceURI is concatenation of java://
and the part of class name before the last dot. E.g. for java.lang.String
targetNamespaceURI is java://java.lang
and targetClass is String
. For classes in default package targetNamespaceURI is java://default
. Actions with blank targetNamespaceURI and blank targetClass are global.
Actions can be associated with each other by implies/impliedBy relationship, e.g. read
action may be implied by update
action.
An action can be grantable or not. Only grantable actions shall be associated with permissions. In combination with implies/impledBy
relationship it allows to have a fine-grained set of actions for authorizing access at code level (e.g. invoke
on a particular operation)
and group such actions together into coarse-grained grantable actions (roles). This approach makes the code independent of
higher level security concerns. I.e. there is no isUserInRole()
method and actions may be moved between roles without having to
change code, e.g. in one deployment of the system action sendMessageToEveryOne
may be implied by Administrator
role (grantable action)
and in another by Power User
role.
Actions can be defined in a scope of one class, but govern access to objects contained by instances of this class. E.g. an action
defined for Customer
may govern access to customer's accounts. It is done by using pathPatterns
. During permission checks AuthorizationHelper
traverses target objects containment and builds a path. E.g. if Account
is contained in Customer.accounts
reference then action
path checked at Account level would be /
, and at the Customer level it would be /accounts
. Action path is matched against
pathPatterns. If pathPatterns collection is empty it defaults to a singleton collection containing /
, i.e. it applies to its permission target.
Actions can have a condition and properties used in condition evaluation. Condition is a JavaScript code. Action matches if its condition evaluates to
true (or if it is empty). Condition code accesses the action properties through actionProperties
variable, the target
object through target
variable. It also has access to environment variables passed to WebContext.authorize()
method.
An example of use of conditional action - Action transfer
on an account can be made conditional depending on on the transfer
amount and transfer
actions with different thresholds can be granted to different principals.
Action can contain other actions. Contained actions are implied by the containing action. They also inherit action attributes except the qualifier
attribute. It allows to build action structures paralleling class structures, e.g. define an action for a class (say, Account
), then a sub-action for operation
invocation inheriting parent action's targetClass and targetNamespace (say, transfer(Account other, BigDecimal amount)
),
and then sub-actions with different conditions or properties and qualifiers (say, amount<$5000, amount<$10000, no limit) and grant those sub-actions to different
roles/groups (say, Clerk, Supervisor, General Manager).
Action name can have class:
prefix to indicate that it is an action on class, not instance. For example if the application needs to check whether
the principal is allows to instantiate mortgage accounts it can do something like this:
if (context.authorize(MortgagePackage.eINSTANCE.getMortgageAccount(), "create", null, null)) {
...
}
It will result in checking for a permission to execute an action with name class:create
.
*
as an action name is a wildcard which matches any name.
Permission is owned by a principal and is associated with an action and optionally with a repository object. Permission can allow or deny its action. Permission can have effective dates. Association of a permission to its action is done not by a reference but by a lookup of an action with the same ActionKey attributes. It allows to store actions is security policies and use different security policies in different application deployments.
Permissions without target apply to all instances of permission's class.
Sub-classes inherit super-classes actions/permissions. During authorization permissions are ordered by distance from the target's class. Permission
defined on a subclass takes precedence over a permission defined on a superclass. For example, transfer
permission can be allowed to clerks
at Account
class but denied at MortgateAccount
class, which is a subclass of Account
.
Dealing with fine-grained permissions can be cumbersome. Model classes can be annotated with org.nasdanika.cdo.security:permissions
annotation
to specify permissions imply
relationship. Detail keys correspond to implying permissions in format <action>/<qualifier>
, and
detail values contain implied permissions <action>/<path/qualifier pattern>
- one permission per line. Implied action name *
matches any action.
Lines starting with #
are considered comment lines.
Implying of permissions works only in the ALLOW direction - allowing the implying permission allows the implied, but denying the implying permission does not deny implied permissions because of the one-to-many relationship between the implying and the implied.
Execution of landing
user story on a class XYZ requires loading of the generated JavaScript module, read access to home and summary rows,
loading of applications modules, and invocation of getSummaryRow getter. The permissions annotation defines a details entry with
story/landing
key and the value shown below.
extension/js
read/home
read/summary
read/applications/getSummaryRow
extension/applications/js
Principals are granted story/landing
"business" permission and it implies "technical" permissions:
guest.getPermissions().add(createPermission("story", hub, "/landing", true));
This class is a binding of ProtectionDomain to LoginPasswordCredentials.
User which uses login and password for authentication.
Helper class for Principal implementations. It should be create with the principal implementation as a constructor argument and the principal's
authorize()
method shall delegate to the helper:
private AuthorizationHelper authorizationHelper = new AuthorizationHelper(this);
public AccessDecision authorize(
SecurityPolicy securityPolicy,
Context context,
Object target,
String action,
String qualifier,
Map<String, Object> environment) {
return authorizationHelper.authorize(securityPolicy, context, target, action, qualifier, environment);
}
SecurityPolicy allows to look-up an action for a permission. Security policies can be provided by extensions and services. ProtectionDomain can also implement SecurityPolicy.
SecurityPolicyContainer is a EClass which implements SecurityPolicy and ActionContainer. It can also import other action containers, including other security policy containers. SecurityPolicyContainer definition can be edited with Nasdanika CDO Security Editor
SecurityPolicyContaner file can be added to the system through policy_resource
extension: