OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
855 stars 473 forks source link

Support omitting properties for permission reasons and setting Core.Permissions instance annotation instead #678

Open vojtechvit opened 8 years ago

vojtechvit commented 8 years ago

[OData-Protocol: 11.2.2 Requesting Individual Entities] "Properties that are not available, for example due to permissions, are not returned. In this case, the Core.Permissions annotation, defined in [OData-VocCore] MUST be returned for the property with a value of Core.Permission'None'."

vojtechvit commented 8 years ago

Ideas on how to implement this:

Special properties on the model following a convention-based name or decorated with a special attribute

Example:

public class User
{
    ...

    public string EmailAddress { get; set; }

    [PermissionsPropertyFor(nameof(EmailAddress))]
    public ODataPermission? EmailAddressPermissions { get; set; }
}

If the value of EmailAddressPermissions property is set to null, Read or ReadWrite, the value of EmailAddress is included in the HTTP response. If the value of EmailAddressPermissions property is set to None, the value of EmailAddress is NOT included in the HTTP response. If the value of EmailAddressPermissions property is set to None, Read or ReadWrite, the value of EmailAddress@Core.Permission instance annotation is included in the HTTP response.

It's an easy to use approach for this particular case, but it requires modifications to the model and is not generic enough to allow setting other instance annotations.

grahamehorner commented 8 years ago

This is a good idea however the model provided should be abstracted into something like database views; a user typically has insert/create select/read update/write delete permissions on a entity set; and execution on methods/database stored procedures.

I think utilising the AuthorizationPolicy filter is possibly the best way to achieve what would amount to a dynamic/user schema for the data source based on claims/roles processed by the filter.

https://channel9.msdn.com/Blogs/Seth-Juarez/ASPNET-Core-Authorization-with-Barry-Dorrans

vojtechvit commented 8 years ago

@grahamehorner Our use case is of a social network where access permissions are user-centric and at the level of individual data entries. For instance, users are authorized to see e-mail addresses only of users that approved it. It's a behavior very similar to Facebook - there you set visibility of every piece of information in your profile individually and consequently that information may or may not be returned to other users in OAuth calls.

This is why we need to use Instance Annotations and not a model-level annotation or split models. There is no other way to do it in a scenario like of an API that returns a list of user profiles where the caller may have access to e-mail addresses of just a subset of the profiles returned.

xuzhg commented 8 years ago

@vojtechvit Can you use: 1. Open type and dynamic property or 2. Custom function/action?

vojtechvit commented 8 years ago

@xuzhg Open types workaround - if we make our types open types in all such scenarios, there will be many basic properties missing from the model. There is not much point in using OData anymore then, because for me the biggest added value is the well-defined model and the ability of client libraries to use it.

Custom functions workaround - in a system like the above described (social network with advanced permission management), we would have to define all GET endpoints this way. Which, again, defeats the point of using OData.

OData v4 standard has defined mechanisms for almost any scenario thinkable. In our company we know the standard very well and we use it wherever possible - and I can tell that we have scenarios that cover almost every feature of the standard, including, for example, asynchronous request processing, media entities, @odata.bind or instance annotations (like in this case). The Web API tooling, however, still doesn't support some of them, which is a shame. Some of these features, like @odata.bind or instance annotations are the only "right way" to do some scenarios; workarounds are usually against the very nature of the standard. This ticket is about one of such features.

vojtechvit commented 8 years ago

By the way - we don't necessarily need LINQ support for this. This feature could be supported by just adding the possibility to exclude properties of some returned objects and to add instance property annotations to some returned object properties.

It could look something like this:

public IHttpActionResult Get(ODataResultProperties odataResultProperties)
{
    var profilesWithMask = _dataAccess.GetProfiles(callingUser: User.Identity.Name);

    foreach(var profile in profilesWithMask.Where(p => p.HiddenProperties.Any())) 
    {
        foreach(var hiddenProperty in profile.HiddenProperties)
        {
            odataResultProperties.ExcludeProperties(profile, hiddenProperty);
            odataResultProperties.AddInstanceAnnotation(profile, hiddenProperty, "Core.Permissions", "Core.Permission'None'");
        }
    }

    return profilesWithMask;
}
engbryan commented 4 years ago

Is there a way to achieve this?

I need to expose a DBContext and just prevent some properties from being read or written depending on a role.