Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for orphaned EntityReferences #5

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions Rhino.Security.Tests/DeleteEntityEventListenerFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace Rhino.Security.Tests
{
using System;
using NHibernate.Criterion;
using Rhino.Security.Model;
using Xunit;

public class DeleteEntityEventListenerFixture : DatabaseFixture
{
[Fact]
public void DoesDeletingEntityRemoveEntityReferences() {
var account = new Account() { Name = "Bob" };
session.Save(account);
authorizationRepository.AssociateEntityWith(account, "Important Accounts");
session.Flush();

Guid securityKey = Security.ExtractKey(account);

// Confirm EntityReference for the new account exists.
EntityReference reference = session.CreateCriteria<EntityReference>()
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.UniqueResult<EntityReference>();

Assert.NotNull(reference);

// Create a permission for the entity
this.permissionsBuilderService.Allow("/Account/Edit").For(this.user).On(account).DefaultLevel().Save();

// Confirm the permissison for the entity exists
Permission permission = session.CreateCriteria<Permission>()
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.UniqueResult<Permission>();

Assert.NotNull(permission);

// Delete account
session.Delete(account);
session.Flush();

// Confirm EntityReference was removed
reference = session.CreateCriteria<EntityReference>()
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.UniqueResult<EntityReference>();

Assert.Null(reference);

// Confirm the permissison for the entity was removed
permission = session.CreateCriteria<Permission>()
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.UniqueResult<Permission>();

Assert.Null(permission);
}
}
}
1 change: 1 addition & 0 deletions Rhino.Security.Tests/Rhino.Security.Tests-vs2008.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
<Compile Include="AuthorizationService_Queries_Fixture.cs" />
<Compile Include="PermissionsServiceFixture.cs" />
<Compile Include="DatabaseFixture.cs" />
<Compile Include="DeleteEntityEventListenerFixture.cs" />
<Compile Include="ScenariosFixture.cs" />
<Compile Include="SecrityInfoFixture.cs" />
<Compile Include="SillyContainer.cs" />
Expand Down
69 changes: 69 additions & 0 deletions Rhino.Security/DeleteEntityEventListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Rhino.Security
{
using System;
using System.Collections.Generic;
using NHibernate.Criterion;
using NHibernate.Event;
using Rhino.Security.Model;

/// <summary>
/// Litenens for when a secured entity is deleted from the system and deletes
/// associated security data.
/// </summary>
[Serializable]
public class DeleteEntityEventListener : IPreDeleteEventListener
{
#region IPreDeleteEventListener Members

/// <summary>
/// Handles PreDelete event to delete an entity's associated security data.
/// </summary>
/// <param name="deleteEvent">Event object containing the delete operation information.</param>
/// <returns>False, indicating the delete operation should not be vetoed.</returns>
public bool OnPreDelete(PreDeleteEvent deleteEvent) {
var securityKey = Security.ExtractKey(deleteEvent.Entity);

if (!Guid.Empty.Equals(securityKey)) {
EntityReference entityReference = deleteEvent.Session.CreateCriteria<EntityReference>()
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.UniqueResult<EntityReference>();

if (entityReference != null) {
var childSession = deleteEvent.Session.GetSession(NHibernate.EntityMode.Poco);

childSession.Delete(entityReference);

//Also remove EntityReferencesToEntitiesGroups and Permissions that reference this entity

//Get list of EntitiesGroups that have the entity as a member
IEnumerable<EntitiesGroup> entitiesGroups = childSession.CreateCriteria<EntitiesGroup>()
.CreateCriteria("Entities")
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.List<EntitiesGroup>();

foreach (EntitiesGroup group in entitiesGroups) {
group.Entities.Remove(entityReference);
}

////Get list of Permissions that references the entity
IEnumerable<Permission> permissions = childSession.CreateCriteria<Permission>()
.Add(Restrictions.Eq("EntitySecurityKey", securityKey))
.SetCacheable(true)
.List<Permission>();

foreach (Permission permission in permissions) {
childSession.Delete(permission);
}

childSession.Flush();
}
}

return false;
}

#endregion
}
}
1 change: 1 addition & 0 deletions Rhino.Security/Rhino.Security-vs2008.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Impl\JetBrains.Annotations.cs" />
<Compile Include="DeleteEntityEventListener.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
57 changes: 45 additions & 12 deletions Rhino.Security/Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using log4net;
using Microsoft.Practices.ServiceLocation;
using NHibernate.Cfg;
using NHibernate.UserTypes;
using NHibernate.Event;
using Rhino.Security.Impl;
using Rhino.Security.Impl.MappingRewriting;
using Rhino.Security.Interfaces;
Expand Down Expand Up @@ -33,11 +33,43 @@ public class Security
public static Guid ExtractKey<TEntity>(TEntity entity)
where TEntity : class
{
Guard.Against<ArgumentNullException>(entity == null, "Entity cannot be null");
Guard.Against<ArgumentNullException>(entity == null, "entity");
var extractor = ServiceLocator.Current.GetInstance<IEntityInformationExtractor<TEntity>>();
return extractor.GetSecurityKeyFor(entity);
}

/// <summary>
/// Extracts the key from the specified entity using the given object.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>If no IEntityInformationExtractor{TEntity} class is registered in the IoC container
/// for the entity's runtime type this returns an empty Guid, otherwise the security
/// key of the given entity.</returns>
/// <remarks>It is recommended to use the ExtractKey{TEntity}(TEntity) method if possible
/// as this code uses reflection to extract the entity security key.</remarks>
internal static Guid ExtractKey(object entity) {
Guard.Against<ArgumentNullException>(entity == null, "entity");
Type[] entityType = { entity.GetType() };
Guard.Against<ArgumentException>(!entityType[0].IsClass, "Entity must be a class object");

Type extractorType = typeof(IEntityInformationExtractor<>);
Type genericExtractor = extractorType.MakeGenericType(entityType);
object extractor = null;

try {
extractor = ServiceLocator.Current.GetInstance(genericExtractor);
}
catch (ActivationException) {
// If no IEntityInformationExtractor is registered then the entity isn't
// secured by Rhino.Security. Ignore the error and return an empty Guid.
return Guid.Empty;
}

object key = genericExtractor.InvokeMember("GetSecurityKeyFor", BindingFlags.InvokeMethod, null, extractor, new object[] { entity });

return (Guid)key;
}

/// <summary>
/// Gets a human readable description for the specified entity
/// </summary>
Expand Down Expand Up @@ -74,15 +106,16 @@ internal static string GetSecurityKeyPropertyInternal<TEntity>()
return ServiceLocator.Current.GetInstance<IEntityInformationExtractor<TEntity>>().SecurityKeyPropertyName;
}

///<summary>
/// Setup NHibernate to include Rhino Security configuration
///</summary>
public static void Configure<TUserType>(Configuration cfg, SecurityTableStructure securityTableStructure)
where TUserType : IUser
{
cfg.AddAssembly(typeof (IUser).Assembly);
new SchemaChanger(cfg, securityTableStructure).Change();
new UserMapper(cfg, typeof(TUserType)).Map();
}
///<summary>
/// Setup NHibernate to include Rhino Security configuration
///</summary>
public static void Configure<TUserType>(Configuration cfg, SecurityTableStructure securityTableStructure)
where TUserType : IUser
{
cfg.AddAssembly(typeof (IUser).Assembly);
new SchemaChanger(cfg, securityTableStructure).Change();
new UserMapper(cfg, typeof(TUserType)).Map();
cfg.SetListener(ListenerType.PreDelete, new DeleteEntityEventListener());
}
}
}