Message Bus¶
Overview¶
In the distributed architecture we are building; in one hand, we want to keep a single master domain for Security concerns, but in the other hand we want to allow other services to use this information without affecting performance and keeping services decoupled. This information usually is quite stable in production and quite simple; if we ignore the calculation of the effective permissions (permissions for a given user of group). Based on above premises, we can assume that allowing other services to manage a local read-only copy of this information is reasonable and quite important for performance reasons (almost required); we will refer to this local read-only version in some applications like the legacy security schema.
This integration is achieved using our service bus. All changes and deletions of groups, roles, user types, securables and users will be notified with a message; also, each time than an effective permissions has changed will be notified.
In the previous diagram we can see how the service bus is used in the Security services. We will need to develop four main components or artifacts:
- Message contracts. A set of messages for defining the different events that can occur in the security service and we need to expose to external services.
- Publisher. We need a mechanism in the Security API that it will react to changes in security configuration and it will send the specific message to the bus.
- Consumer for security services. Authorization server needs to cache the effective permissions provided by Security API; so, it's required a mechanism to invalidate this cached information. So, we need a bus consumer to react to events for invalidating the cached information. Also, Authentication services could have cached data and it will need to react to changes in order to invalidate this information.
- Consumer for "external services". External services (as Workflow or Product Builder) will need to keep some security information in sync. So, they will need to build a consumer for reacting to those events. As part of the work done in Supporting Apps Team we will develop this consumer in Product Backlog Item 113260:Security Messages Consumer for WF & PB
1. Message contracts¶
Message contracts will be defined in Sequel.Security.MessageBus.Contracts project and this will be published via NuGet.
General concerns¶
There are some concerns when we define the contracts that we have to cover:
-
A message contract per event. In our case, that means that we will have two different messages for each entity: changed and deleted. Changed includes created event; as both events must be managed in the consumer with the same approach: create if not exists or update.
-
Contracts must be versioned based on different interface implementations; we will use namespaces to achieve it, following this name convention:
Sequel.Security.MessageBus.Contracts.{MessageName}.v{Version}
Sequel.Security.MessageBus.Contracts.Securables.v1
Sequel.Security.MessageBus.Contracts.Securables.v2
Sequel.Security.MessageBus.Contracts.Groups.v1
- Don't assume message will be consumed in sequence: We need to add a SequenceId.
/// <summary>
/// Defines a class that contains a SequenceId for sequencing messages
/// </summary>
/// <typeparam name="T">Class type of the object created or changed</typeparam>
public interface ISequencedMessage
{
string SequenceId { get; set; }
}
DateTime.UtcNow.ToString("yyyyMMddHHmmssffffff", CultureInfo.InvariantCulture)
//20180525143621434686 -- Lenght 20
- Multi-tenant messages.
As security information is divided by tenants; this information must be included for all messages, as this will allow to the subscribers to filter by some types of messages. We can resume common information in below interface:
public interface ITenantAwareMessage
{
string TenantId { get; set; }
}
Architecture catch-up required: If we want to deploy service bus as multi-tenant resources; we can implemented it in RabbitMQ using virtual host; and in Microsoft using namespaces. However, this will required to implement our consumers to connect to multiple hosts. This will required some input some Architecture team.
- Application base messages.
As most of the security information is divided by applications; this information must be included for some messages, as this will allow to subscribers to filter by some types of messages. We can resume common information in below interface:
public interface ISecurityMessageContractBase {
string ApplicationKey { get; set; } //For filtering
string Code { get; set; }
string Key { get; set; } //Key = ApplicationKey + . + Code
}
Architecture catch-up required: Current implementations of Sequel.Core.MessageBus doesn't provide support for filtering messages.
Messages¶
Effective permissions¶
-
IEffectivePermissionChangedMessage. Effective permissions for a specific application has changed. Change could affect to a single user or a list of groups. This message is published:
-
When membership collection changes for a user: publish a message with the affected user, group and application.
- When permissions associated to a role changes: publish a message with the application affected and the groups affected; UserName is null. To calculate the affected groups we need to detect all different groups associated to this role in the membership table.
The content of this message is directly defined by the endpoints offered by SecurityAPI Service (at the EffectivePermission); where effective permissions are mainly accessed by application, user and a list of groups. Managing this message in this way we can allow to other applications to cache responses and invalidate them; and at the same time, keep all the logic for calculating effective permissions internally at the security services. The time to live for this messages is set to 30 minutes by default.
public interface IEffectivePermissionChangedMessage : ITenantAwareMessage, ISequencedMessage
{
/// <summary>
/// Determines ApplicationKey where permissions has changed.
/// </summary>
string ApplicationKey { get; set; }
/// <summary>
/// Determines the groups affected by the change in effective permissions.
/// </summary>
List<string> GroupKeys { get; set; }
/// <summary>
/// Determines the user affected by the change in effective permissions.
/// If null means that affects to all applications.
/// </summary>
string UserName { get; set; }
}
Forgot password¶
- IForgotPasswordChangedMessage. This message is published when a user make a request to change the password.
The content of this message is defined by the user data. The time to live for this messages is set to 10 minutes by default.
public interface IForgotPasswordChangedMessage : ITenantAwareMessage, ISequencedMessage
{
string Name { get; set; }
string EmailAdress { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
string UrlBack { get; set; }
}
Entities changed or deleted: groups, roles, securable, user and user types.¶
The purpose of this messages is to allow other systems to synchronize security information. Time to live parameter is set to 12 hours by default.
Messages related to creation and modification are based on:
public interface IObjectChangedMessage<T> : ITenantAwareMessage, ISequencedMessage
{
/// <summary>
/// Gets or sets the data of the
/// </summary>
T Data { get; set; }
}
public interface IObjectDeletedMessage<K> : ITenantAwareMessage, ISequencedMessage
{
K Key { get; set; }
}
With above abstractions, we can define below messages; inheriting from IObjectChangedMessage<T>
or IObjectDeletedMessage<K>
:
- IGroupChangedMessage. A new group has been created or an existing has been modified. Where Data is:
public interface IGroup : ISecurityMessageContractBase
{
bool IsSystem { get; set; }
string Name { get; set; }
bool AutomaticallyCreated { get; set; }
}
class GroupChangeSampledMessage : IObjectChangedMessage<Group>, v1.IGroupChangedMessage
{
public Group Data { get; set; } //Group implements IGroup
public string TenantId { get; set; }
public string SequenceId { get; set; }
}
-
IGroupDeletedMessage. A group has been deleted.
-
IRoleChangedMessage. A new role has been created or an existing has been modified. Where Data is:
public interface IRole : ISecurityMessageContractBase
{
bool IsSystem { get; set; }
string Name { get; set; }
}
-
IRoleDeletedMessage. A role has been deleted.
-
ISecurableChangedMessage. A new securable has been created or an existing has been modified. Where Data is:
public interface ISecurable : ISecurityMessageContractBase
{
bool IsSystem { get; set; }
string Name { get; set; }
string CreateDescription { get; set; }
string DeleteDescription { get; set; }
string Description { get; set; }
bool IsCreateAllowed { get; set; }
bool IsDeleteAllowed { get; set; }
bool IsReadAllowed { get; set; }
bool IsUpdateAllowed { get; set; }
string ReadDescription { get; set; }
string UpdateDescription { get; set; }
}
-
ISecurableDeletedMessage. A securable has been deleted.
-
IUserChangedMessage. A new user has been created or an existing has been modified. This message is also published when membership or the user types assigned for the user has changed. Where Data is defined by:
public interface IUser
{
string Name { get; set; }
string EmailAddress { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
string PhoneNumber { get; set; }
bool AbsenceInd { get; set; }
bool AutomaticallyCreated { get; set; }
bool LockoutEnabled { get; set; }
DateTime? LastLoginDateUtc { get; set; }
DateTime? ActiveEndDateUtc { get; set; }
DateTime? PasswordExpiryDateUtc { get; set; }
DateTime? LockoutEndDateUtc { get; set; }
List<IMembership> Memberships { get; set; }
List<IAssignedUserType> AssignedUserTypes { get; set; }
}
public interface IMembership
{
string GroupKey { get; set; }
string RoleKey { get; set; }
}
public interface IAssignedUserType
{
string UserTypeKey { get; set; }
}
-
IUserDeletedMessage. A user has been deleted.
-
IUserTypeChangedMessage. A new user type has been created or an existing has been modified. Where Data is defined as:
public interface IUserType : ISecurityMessageContractBase
{
bool IsSystem { get; set; }
string Name { get; set; }
}
-
IUserTypeDeletedMessage. A user type has been deleted.
-
IConfigurationChangedMessage. A message that contains current Authorization and/or Users configuration:
public interface IConfiguration
{
IUsersConfiguration Users { get; set; }
IDictionary<string, IAuthorizationConfiguration> Applications { get; set; }
}
public interface IUsersConfiguration : IEnumerable<IUser>
{
}
public interface IAuthorizationConfiguration
{
IEnumerable<ISecurable> Securables { get; }
IEnumerable<IUserType> UserTypes { get; }
IEnumerable<IRole> Roles { get; }
IEnumerable<IGroup> Groups { get; }
}
2. Publisher¶
At the Security API we need to develop a publisher for sending above messages to the bus. Triggering scenarios are described above in the message section.
Architecture catch-up required: Concerns here: exchange naming convention and how to support filtering by application and multi-tenant (previously exposed).
3. Consumer for internal Security Services¶
Consumers and caching¶
One purpose of consumers in security is caching information. We will follow below approaches:
-
We will use in memory cache from ASP.NET Core.
-
Cache will be enabled by default via configuration (cache:enabled = true). So, we will be able to disable if required.
-
Cache will be invalidated consuming messages from the bus; the logic to invalidate the bus is:
The logic for calculating the key depends of each message; for messages inheriting from ISecurityMessageContractBase
the Key is clearly defined; for User messages the Key is the Id; and for Effective Permissions the key is based on ApplicationKey, UserId and GroupIds (it will depend on how this application is managing the cache).
Consumer for Authorisation service¶
EffectivePermissionsChanged will be consumed by Authorisation service, in order to invalidate the EffectivePermissions cached.
Consuming a message will need to ensure this message is newest than the previous one; and, in this case invalidate the changed data. The information cached will be based on TenantId, ApplicationKey, UserId and GroupIds.