Skip to main content
Version: 8.3

Record permission extension patterns

Level: advanced
note

This functionality is available for Creatio 8.3.4 and later.

The following patterns demonstrate how to apply a record permission extension to common business scenarios. Each pattern includes the source code and the corresponding Record permission extension lookup configuration. Adapt one or more "Object" type schemas, attributes, and conditions to your business needs.

Before you use these patterns, implement the extension class and configure it in the lookup. Instructions: Implement a custom record permission extension.

Combine the "Read" and "Edit" operations in one class

Use this pattern when the same business rule must control both the "Read" and "Edit" operations.

The following example implements the "Read" and "Edit" operations using a shared condition in a single class for the "Contact" (Contact code) object.

Implement the "Read" and "Edit" operations using a shared condition in a single class
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" and "Edit" operations. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrContactOwnerReadEditExtension))]
[DefaultBinding(typeof(IEditRecordPermissionExtension),
Name = nameof(UsrContactOwnerReadEditExtension))]
public class UsrContactOwnerReadEditExtension :
IReadRecordPermissionExtension, IEditRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
return BuildOwnerCondition(userConnection, context.SchemaName);
}
/* Build the condition for the "Edit" operation. */
public QueryCondition GetEditRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
return BuildOwnerCondition(userConnection, context.SchemaName);
}
/* Build the shared condition. */
private QueryCondition BuildOwnerCondition(
UserConnection userConnection,
string entitySchemaSourceAlias) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
entitySchemaSourceAlias, "CreatedById")
};
condition.IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId));
return condition;
}
}
}

The following example registers the extension in the Record permission extension lookup.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Contact

And

Read

UsrContactOwnerReadEditExtension

Yes

10

Contact

And

Edit

UsrContactOwnerReadEditExtension

Yes

10

Separate the "Read," "Edit," and "Delete" operations into individual classes

Use this pattern when the operations must follow different business rules.

The following example implements the "Read" operation for the "Account" (Account code) object.

Implement the "Read" operation
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrAccountReadPermissionExtension))]
public class UsrAccountReadPermissionExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
context.SchemaName, "OwnerId")
};
condition.IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId));
return condition;
}
}
}

The following example implements the "Edit" operation for the "Account" (Account code) object.

Implement the "Edit" operation
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Edit" operation. */
[DefaultBinding(typeof(IEditRecordPermissionExtension),
Name = nameof(UsrAccountEditPermissionExtension))]
public class UsrAccountEditPermissionExtension :
IEditRecordPermissionExtension
{
/* Build the condition for the "Edit" operation. */
public QueryCondition GetEditRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
context.SchemaName, "PrimaryContactId")
};
condition.IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId));
return condition;
}
}
}

The following example implements the "Delete" operation for the "Account" (Account code) object.

Implement the "Delete" operation
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Delete" operation. */
[DefaultBinding(typeof(IDeleteRecordPermissionExtension),
Name = nameof(UsrAccountDeletePermissionExtension))]
public class UsrAccountDeletePermissionExtension :
IDeleteRecordPermissionExtension
{
/* Build the condition for the "Delete" operation. */
public QueryCondition GetDeleteRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
context.SchemaName, "CreatedById")
};
condition.IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId));
return condition;
}
}
}

The following example registers the extension in the Record permission extension lookup.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Account

Or

Read

UsrAccountReadPermissionExtension

Yes

10

Account

And

Edit

UsrAccountEditPermissionExtension

Yes

10

Account

InsteadOf

Delete

UsrAccountDeletePermissionExtension

Yes

10

Use a correlated EXISTS subquery

Use this pattern when the rule is easier to express through a correlated subquery — for example, when the outer query alias must be referenced directly, a simple column comparison is not enough, or the logic is naturally expressed by EXISTS.

The following example uses a correlated EXISTS subquery to build the condition for the "Account" (Account code) object. Give the subquery a unique inner alias so it does not collide with schema names or aliases already used in the final SQL.

Use a correlated "EXISTS" subquery to build the condition
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrAccountExistsReadExtension))]
public class UsrAccountExistsReadExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
string schemaName = context.SchemaName;
string schemaNameAlias = $"{schemaName}_X7F2";
Select select = new Select(userConnection)
.Column(Column.Parameter(1))
.From("Account").As(schemaNameAlias)
.Where(schemaNameAlias, "CreatedById").IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId))
.And(schemaNameAlias, "Id").IsEqual(
schemaName, "Id") as Select;
QueryCondition condition = new QueryCondition();
condition.Exists(select);
return condition;
}
}
}

The following example registers the extension in the Record permission extension lookup.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Account

And

Read

UsrAccountExistsReadExtension

Yes

10

Apply one extension to several "Object" type schemas

Use this pattern when the same "Read," "Edit," or "Delete" rule must apply to several "Object" type schemas.

The following example applies the same "Read" operation rule to both the "Contact" (Contact code) and "Account" (Account code) objects.

Apply the same "Read" operation rule to both the "Contact" and "Account" objects
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrOwnerReadPermissionExtension))]
public class UsrOwnerReadPermissionExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
context.SchemaName, "CreatedById")
};
condition.IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId));
return condition;
}
}
}

The following example registers the extension in the Record permission extension lookup. One lookup value targets both the "Contact" and "Account" "Object" type schemas, and Creatio uses the same extension class for both. Creatio still applies the rule separately per schema. If one of these schemas later needs different behavior, create a separate extension or a separate higher-priority row for that operation and schema.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Contact;Account

And

Read

UsrOwnerReadPermissionExtension

Yes

10

Filter records by a numeric attribute threshold

Use this pattern when access must depend on a numeric record attribute compared against a fixed threshold.

The following example restricts the "Read" operation for "Contact" (Contact code) object records to those with an age greater than 30.

Restrict the "Read" operation for "Contact" object records
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrContactAgeReadPermissionExtension))]
public class UsrContactAgeReadPermissionExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
context.SchemaName, "Age")
};
condition.IsGreater(Column.Parameter(30));
return condition;
}
}
}

The following example registers the extension in the Record permission extension lookup.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Contact

And

Read

UsrContactAgeReadPermissionExtension

Yes

10

Filter records by a text pattern

Use this pattern when access must depend on whether a text attribute matches a pattern.

The following example restricts the "Read" operation for "Contact" (Contact code) object records whose name matches a pattern.

Restrict the "Read" operation for "Contact" object records
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrContactNameLikeReadPermissionExtension))]
public class UsrContactNameLikeReadPermissionExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
var condition = new QueryCondition {
LeftExpression = new QueryColumnExpression(
context.SchemaName, "Name")
};
condition.IsLike(Column.Parameter("%Ann%"));
return condition;
}
}
}

The following example registers the extension in the Record permission extension lookup.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Contact

And

Read

UsrContactNameLikeReadPermissionExtension

Yes

10

Filter records by a related attribute of the current user

Use this pattern when a user must see only records related to that user's own attribute, such as a region.

The following example restricts the "Read" operation for "Contact" (Contact code) object records to the current user's region.

Restrict the "Read" operation for "Contact" object
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrContactSameRegionReadPermissionExtension))]
public class UsrContactSameRegionReadPermissionExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
string schemaName = context.SchemaName;
string schemaNameAlias = $"{schemaName}_P9R3";
var subSelect = new Select(userConnection)
.Column(Column.Parameter(1))
.From(schemaName).As(schemaNameAlias)
.Where(schemaNameAlias, "Id").IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId))
.And(schemaNameAlias, "RegionId").IsEqual(
Column.SourceColumn(schemaName, "RegionId")
) as Select;
var condition = new QueryCondition();
condition.Exists(subSelect);
return condition;
}
}
}

The following example restricts the "Read" operation for "Account" (Account code) object records to the current user's region.

Restrict the "Read" operation for "Account" object records
namespace Terrasoft.Configuration
{
using Terrasoft.Core;
using Terrasoft.Core.DB;
using Terrasoft.Core.Factories;
using Terrasoft.Core.RecordPermissionExtension;

/* Register the extension for the "Read" operation. */
[DefaultBinding(typeof(IReadRecordPermissionExtension),
Name = nameof(UsrAccountSameRegionReadPermissionExtension))]
public class UsrAccountSameRegionReadPermissionExtension :
IReadRecordPermissionExtension
{
/* Build the condition for the "Read" operation. */
public QueryCondition GetReadRightCondition(
UserConnection userConnection,
PermissionExtensionContext context) {
string schemaName = context.SchemaName;
string contactAlias = $"Contact_P9R3";
var subSelect = new Select(userConnection)
.Column(Column.Parameter(1))
.From("Contact").As(contactAlias)
.Where(contactAlias, "Id").IsEqual(
Column.Parameter(userConnection.CurrentUser.ContactId)
)
.And(contactAlias, "RegionId").IsEqual(
Column.SourceColumn(schemaName, "RegionId")
) as Select;
var condition = new QueryCondition();
condition.Exists(subSelect);
return condition;
}
}
}

The following example registers both extensions in the Record permission extension lookup.

Schema names

Extension combination mode

Record permission operation type

Extension name

Is active

Position

Contact

And

Read

UsrContactSameRegionReadPermissionExtension

Yes

10

Account

And

Read

UsrAccountSameRegionReadPermissionExtension

Yes

10


See also

Record permissions (user documentation)

Extending record permissions

Implement a custom record permission extension