Skip to content

Add OData authorization project #1

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

Merged
merged 2 commits into from
Jun 29, 2020
Merged
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
21 changes: 21 additions & 0 deletions AspNetCoreOData.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataRoutingSample", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OData.Formatting", "src\Microsoft.AspNetCore.OData.Formatting\Microsoft.AspNetCore.OData.Formatting.csproj", "{0D812D96-0F07-4314-BA0E-262B62385AEA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OData.Authorization", "src\Microsoft.AspNetCore.OData.Authorization\Microsoft.AspNetCore.OData.Authorization.csproj", "{6C0965A2-E024-43DA-A725-F3ECD987BA28}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OData.Authorization.Tests", "test\Microsoft.AspNetCore.OData.Authorization.Tests\Microsoft.AspNetCore.OData.Authorization.Tests.csproj", "{62367BA7-8A0C-436D-B51B-C0B5AB6303B8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ODataAuthorizationSample", "sample\ODataAuthorizationSample\ODataAuthorizationSample.csproj", "{86248CD5-C9B6-46BA-9F78-F1CF009B17F7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,6 +45,18 @@ Global
{0D812D96-0F07-4314-BA0E-262B62385AEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D812D96-0F07-4314-BA0E-262B62385AEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D812D96-0F07-4314-BA0E-262B62385AEA}.Release|Any CPU.Build.0 = Release|Any CPU
{6C0965A2-E024-43DA-A725-F3ECD987BA28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C0965A2-E024-43DA-A725-F3ECD987BA28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C0965A2-E024-43DA-A725-F3ECD987BA28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C0965A2-E024-43DA-A725-F3ECD987BA28}.Release|Any CPU.Build.0 = Release|Any CPU
{62367BA7-8A0C-436D-B51B-C0B5AB6303B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62367BA7-8A0C-436D-B51B-C0B5AB6303B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62367BA7-8A0C-436D-B51B-C0B5AB6303B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62367BA7-8A0C-436D-B51B-C0B5AB6303B8}.Release|Any CPU.Build.0 = Release|Any CPU
{86248CD5-C9B6-46BA-9F78-F1CF009B17F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{86248CD5-C9B6-46BA-9F78-F1CF009B17F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86248CD5-C9B6-46BA-9F78-F1CF009B17F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86248CD5-C9B6-46BA-9F78-F1CF009B17F7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -48,6 +66,9 @@ Global
{FE9DAF68-76F4-4160-A5FF-B6644D65246A} = {2F0E102B-EB33-4025-BE56-7B8F9D2C4B8A}
{A949AA11-2C07-490C-8F07-8A87A2B2E0B8} = {B1F86961-6958-4617-ACA4-C231F95AE099}
{0D812D96-0F07-4314-BA0E-262B62385AEA} = {2F0E102B-EB33-4025-BE56-7B8F9D2C4B8A}
{6C0965A2-E024-43DA-A725-F3ECD987BA28} = {2F0E102B-EB33-4025-BE56-7B8F9D2C4B8A}
{62367BA7-8A0C-436D-B51B-C0B5AB6303B8} = {F994C269-55BA-44F0-9DA7-6D5A3CFA79EB}
{86248CD5-C9B6-46BA-9F78-F1CF009B17F7} = {B1F86961-6958-4617-ACA4-C231F95AE099}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {540C9752-AAC0-49EA-BA60-78490C90FF86}
Expand Down
119 changes: 119 additions & 0 deletions sample/ODataAuthorizationSample/Controllers/CustomersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using ODataAuthorizationSample.Models;
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ODataAuthorizationSample.Controllers
{
public class CustomersController : ODataController
{
private readonly AppDbContext _context;

public CustomersController(AppDbContext context)
{
_context = context;

if (_context.Customers.Count() == 0)
{
IList<Customer> customers = new List<Customer>
{
new Customer
{
Name = "Jonier",
HomeAddress = new Address { City = "Redmond", Street = "156 AVE NE"},
FavoriteAddresses = new List<Address>
{
new Address { City = "Redmond", Street = "256 AVE NE"},
new Address { City = "Redd", Street = "56 AVE NE"},
},
Order = new Order { Title = "104m" },
Orders = Enumerable.Range(0, 2).Select(e => new Order { Title = "abc" + e }).ToList()
},
new Customer
{
Name = "Sam",
HomeAddress = new Address { City = "Bellevue", Street = "Main St NE"},
FavoriteAddresses = new List<Address>
{
new Address { City = "Red4ond", Street = "456 AVE NE"},
new Address { City = "Re4d", Street = "51 NE"},
},
Order = new Order { Title = "Zhang" },
Orders = Enumerable.Range(0, 2).Select(e => new Order { Title = "xyz" + e }).ToList()
},
new Customer
{
Name = "Peter",
HomeAddress = new Address { City = "Hollewye", Street = "Main St NE"},
FavoriteAddresses = new List<Address>
{
new Address { City = "R4mond", Street = "546 NE"},
new Address { City = "R4d", Street = "546 AVE"},
},
Order = new Order { Title = "Jichan" },
Orders = Enumerable.Range(0, 2).Select(e => new Order { Title = "ijk" + e }).ToList()
},
};

foreach (var customer in customers)
{
_context.Customers.Add(customer);
_context.Orders.Add(customer.Order);
_context.Orders.AddRange(customer.Orders);
}

_context.SaveChanges();
}
}

[EnableQuery]
public IActionResult Get()
{
// Be noted: without the NoTracking setting, the query for $select=HomeAddress with throw exception:
// A tracking query projects owned entity without corresponding owner in result. Owned entities cannot be tracked without their owner...
_context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

return Ok(_context.Customers);
}

[EnableQuery]
public IActionResult Get(int key)
{
return Ok(_context.Customers.FirstOrDefault(c => c.Id == key));
}

/// <summary>
/// If testing in IISExpress with the POST request to: http://localhost:2087/test/my/a/Customers
/// Content-Type : application/json
/// {
/// "Name": "Jonier","
/// }
///
/// Check the reponse header, you can see
/// "Location" : "http://localhost:2087/test/my/a/Customers(0)"
/// </summary>
[EnableQuery]
public IActionResult Post([FromBody]Customer customer)
{
return Created(customer);
}

public IActionResult Delete(int key)
{
var customer = _context.Customers.FirstOrDefault(c => c.Id == key);
_context.Customers.Remove(customer);
return Ok(customer);
}

[ODataRoute("GetTopCustomer")]
public IActionResult GetTopCustomer()
{
return Ok(_context.Customers.FirstOrDefault());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace ODataAuthorizationSample.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

private readonly ILogger<WeatherForecastController> _logger;

public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
13 changes: 13 additions & 0 deletions sample/ODataAuthorizationSample/Models/Address.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace ODataAuthorizationSample.Models
{
[Owned, ComplexType]
public class Address
{
public string City { get; set; }

public string Street { get; set; }
}
}
21 changes: 21 additions & 0 deletions sample/ODataAuthorizationSample/Models/AppDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;

namespace ODataAuthorizationSample.Models
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}

public DbSet<Customer> Customers { get; set; }

public DbSet<Order> Orders { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().OwnsOne(c => c.HomeAddress).WithOwner();
}
}
}
78 changes: 78 additions & 0 deletions sample/ODataAuthorizationSample/Models/AppModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Microsoft.AspNet.OData.Builder;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
using System.Linq;

namespace ODataAuthorizationSample.Models
{
public class AppModel
{
public static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
builder.Function("GetTopCustomer").ReturnsFromEntitySet<Customer>("Customers");

var model = builder.GetEdmModel();
AddPermissions(model as EdmModel);

return model;
}

private static void AddPermissions(EdmModel model)
{
var readRestrictions = "Org.OData.Capabilities.V1.ReadRestrictions";
var insertRestrictions = "Org.OData.Capabilities.V1.InsertRestrictions";
var updateRestrictions = "Org.OData.Capabilities.V1.UpdateRestrictions";
var deleteRestrictions = "Org.OData.Capabilities.V1.DeleteRestrictions";
var operationRestrictions = "Org.OData.Capabilities.V1.OperationRestrictions";

var customers = model.FindDeclaredEntitySet("Customers");
var getTopCustomer = model.SchemaElements.OfType<IEdmOperation>().First(o => o.Name == "GetTopCustomer");


model.AddVocabularyAnnotation(new EdmVocabularyAnnotation(
customers,
model.FindTerm(readRestrictions),
new EdmRecordExpression(
CreatePermissionProperty(new string[] { "Customers.Read", "Product.ReadAll" }),
new EdmPropertyConstructor("ReadByKeyRestrictions", CreatePermission(new[] { "Customers.ReadByKey" })))));

AddPermissionsTo(model, customers, insertRestrictions, "Customers.Insert");
AddPermissionsTo(model, customers, updateRestrictions, "Customers.Update");
AddPermissionsTo(model, customers, deleteRestrictions, "Customers.Delete");
AddPermissionsTo(model, getTopCustomer, operationRestrictions, "Customers.GetTop");
}

public static void AddPermissionsTo(EdmModel model, IEdmVocabularyAnnotatable target, string restrictionName, params string[] scopes)
{
model.AddVocabularyAnnotation(new EdmVocabularyAnnotation(
target,
model.FindTerm(restrictionName),
CreatePermission(scopes)));
}

public static IEdmExpression CreatePermission(params string[] scopeNames)
{
var restriction = new EdmRecordExpression(
CreatePermissionProperty(scopeNames));

return restriction;
}

public static IEdmPropertyConstructor CreatePermissionProperty(params string[] scopeNames)
{
var scopes = scopeNames.Select(scope => new EdmRecordExpression(
new EdmPropertyConstructor("Scope", new EdmStringConstant(scope)),
new EdmPropertyConstructor("RestrictedProperties", new EdmStringConstant("*"))));

var permission = new EdmRecordExpression(
new EdmPropertyConstructor("SchemeName", new EdmStringConstant("AuthScheme")),
new EdmPropertyConstructor("Scopes", new EdmCollectionExpression(scopes)));

var property = new EdmPropertyConstructor("Permissions", new EdmCollectionExpression(permission));
return property;
}
}
}
20 changes: 20 additions & 0 deletions sample/ODataAuthorizationSample/Models/Customer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;

namespace ODataAuthorizationSample.Models
{
public class Customer
{
public int Id { get; set; }

public string Name { get; set; }

public virtual Address HomeAddress { get; set; }

public virtual IList<Address> FavoriteAddresses { get; set; }

public virtual Order Order { get; set; }

public virtual IList<Order> Orders { get; set; }
}

}
8 changes: 8 additions & 0 deletions sample/ODataAuthorizationSample/Models/Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ODataAuthorizationSample.Models
{
public class Order
{
public int Id { get; set; }
public string Title { get; set; }
}
}
19 changes: 19 additions & 0 deletions sample/ODataAuthorizationSample/ODataAuthorizationSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's target .net5

</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OData" Version="7.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.5" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.OData.Authorization\Microsoft.AspNetCore.OData.Authorization.csproj" />
</ItemGroup>


</Project>
26 changes: 26 additions & 0 deletions sample/ODataAuthorizationSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ODataAuthorizationSample
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Loading