using System;
using System.Security;
using PostSharp.Samples.Authorization.BusinessObjects;
using PostSharp.Samples.Authorization.Framework;
using PostSharp.Samples.Authorization.RoleBased;
using SecurityContext = PostSharp.Samples.Authorization.Framework.SecurityContext;
 
namespace PostSharp.Samples.Authorization
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var securityPolicy = new RoleBasedSecurityPolicy();
 
      // Set up role-permission assignments. Depending on your application, this can be hardcoded or stored in a database.
 
      // By default, everybody can read an entity but only the owner can write it.
      securityPolicy.AddRolePermissionAssignment(typeof(object), Permission.Read, Role.Everyone,
        PermissionAction.Grant);
      securityPolicy.AddRolePermissionAssignment(typeof(object), Permission.Write, Role.Owner, PermissionAction.Grant);
      securityPolicy.AddRolePermissionAssignment(typeof(object), Permission.Assign, Role.Owner, PermissionAction.Grant);
 
      // Sales managers have Write and Assign rights to invoices in their business unit.
      securityPolicy.AddRolePermissionAssignment(typeof(Invoice), Permission.Write, Role.SalesManager,
        PermissionAction.Grant);
      securityPolicy.AddRolePermissionAssignment(typeof(Invoice), Permission.Assign, Role.SalesManager,
        PermissionAction.Grant);
 
      // Administrators have the right to assign roles.
      securityPolicy.AddRolePermissionAssignment(typeof(object), Permission.ManageRoles, Role.Administrator,
        PermissionAction.Grant);
 
 
      // Set up an object graph. This would typically be stored in a database.
      // Note that security is disabled at this point because SecurityContext.Current is null.
      var company = new BusinessUnit {Name = "Contoso s.r.o."};
 
      var mikki = new User(Guid.NewGuid()) {Name = "Mikki Grisham"};
      company.UserRoleAssignments.Add(mikki, Role.Everyone);
 
 
      var silva = new User(Guid.NewGuid()) {Name = "Silva Pollard"};
      company.UserRoleAssignments.Add(silva, Role.Everyone);
 
      var admin = new User(Guid.NewGuid()) {Name = "Administrator"};
      company.UserRoleAssignments.Add(admin, Role.Everyone);
      company.UserRoleAssignments.Add(admin, Role.Administrator);
 
 
      var department = new BusinessUnit {ParentUnit = company, Name = "Trolls & gnomes wholesale"};
      var invoice = new Invoice
      {
        Owner = mikki,
        BusinessUnit = department,
        Amount = 50,
        Description = "Kuroji trolls XXL"
      };
 
 
      // Now enable security.
      var context = new SimpleSecurityContext
      {
        Policy = securityPolicy
      };
 
      SecurityContext.Current = context;
 
 
      // Test some operations.
      context.Subject = mikki;
      ShouldNotThrow(() => invoice.Amount = 53, "Changing the invoice amount as mikki");
 
      context.Subject = silva;
      ShouldThrow(() => invoice.Amount = 53, "Changing the invoice amount as silva");
 
      context.Subject = mikki;
      ShouldThrow(() => department.UserRoleAssignments.Add(silva, Role.SalesManager), "Changing roles as mikki");
 
      context.Subject = admin;
      ShouldNotThrow(() => department.UserRoleAssignments.Add(silva, Role.SalesManager), "Changing roles as admin");
 
 
      context.Subject = silva;
      ShouldNotThrow(() => invoice.Amount = 53, "Changing the invoice amount as silva");
    }
 
    public static void ShouldThrow(Action action, string description)
    {
      try
      {
        action();
 
        Console.WriteLine($"BAD. The operation '{description}' has succeeded but should have failed.");
      }
      catch (SecurityException e)
      {
        Console.WriteLine($"GOOD. The operation '{description}' has failed as expected: {e.Message}");
      }
    }
 
    public static void ShouldNotThrow(Action action, string description)
    {
      try
      {
        action();
 
        Console.WriteLine($"GOOD. The operation '{description}' has succeeded as expected.");
      }
      catch (SecurityException e)
      {
        Console.WriteLine($"BAD. The operation '{description}' has failed: {e.Message}");
      }
    }
 
 
    private class SimpleSecurityContext : ISecurityContext
    {
      public ISubject Subject { get; set; }
      public ISecurityPolicy Policy { get; set; }
      public ISecurityExceptionHandler ExceptionHandler { get; set; }
    }
  }
}