Autorisatie en permissies
Autorisatiemodel
Het nieuwe autorisatiemodel in SUMMIT koppelt permissies los van acties die een gebruiker mag uitvoeren binnen SUMMIT. Dit is gedaan omdat er in het huidige permissiemodel vaak geen één-op-één mapping is tussen de actie die een gebruiker uit wil voeren en de bijbehorende permissie; vaak worden er meerdere permissies gecheckt of is er complexe logica nodig om een gebruiker ergens voor te kunnen autoriseren, waarbij soms zelfs niet eens een permissiecheck nodig is.
Het is de bedoeling om zoveel mogelijk de controles uit te voeren in de ApiAcion handlers. Dit is niet voor alles mogelijk, met name bij servermode overzichten, maar de primaire bepaling of een api aangeroepen mag worden hoort in de ApiAction handler thuis.
API Actions
Het nieuwe model maakt gebruik van “API actions”. Een action omschrijft de handeling die een gebruiker uit wil voeren, bijvoorbeeld “View reference tables” of “Move information requests to recycle bin”. Achterliggend aan deze action kan er willekeurige code uitgevoerd worden om te checken of een gebruiker geautoriseerd is om deze action uit te voeren. Deze code noemen we “handlers”.
Om de implementatie voor developers makkelijker te maken, en om veelvoorkomende scenario’s te ondersteunen, zijn er wat helpers gemaakt die ervoor zorgen dat er niet altijd custom code geschreven hoeft te worden. Als een actie slechts een simpele permissiecheck hoeft te doen, hoeft hiervoor geen handler gemaakt te worden; daar zijn convenience methods voor. Ook kan het voorkomen dat een actie één of meerdere andere (parent) actions moet uitvoeren om de autorisatie te kunnen doen. Hiervoor zijn eveneens convenience methods aanwezig.
Een API action mag maar één handler hebben. Dit is één van de volgende soorten:
- een permission check, met of zonder parent action(s);
- één of meerdere parent actions zonder eigen permissiecheck;
- een custom handler.
Controller actions
Om een API action autorisatiecheck toe te passen op een action in een custom controller, moet gebruik gemaakt worden van het ApiActionAttribute. Dit attribuut neemt één argument, namelijk de action die gecheckt moet worden:
[HttpGet, ApiAction(ApiAction.ViewPersons)]
public IActionResult GetSpecialPersons()
{
return Json(_personService.GetSpecialPersons());
}
Indien een controller action géén API action check hoeft te doen, is er het NoApiActionAttribute. Deze moet toegepast worden op controller actions die expliciet geen autorisatiecheck hoeven te doen. Dit attribuut mag ALLEEN gebruikt worden indien er helemaal géén autorisatiecheck nodig is voor deze controller action! Dit betekent dat het gebruik van dit attribuut in principe een uitzonderingssituatie is (behoudens overzichtsqueries).
[HttpGet, NoApiAction]
public IActionResult WeDontNeedNoAuthorization()
{
return Ok();
}
In de test suite van SUMMIT zal gekeken worden of alle controller actions een ApiActionAttribute of NoApiActionAttribute hebben. De build zal falen indien er controller actions worden gevonden zonder één van deze twee attributen. Deze tests zullen op een later moment aangezet worden.
Base CRUD
Voor de base CRUD acties wordt er door de .NET generator een mapping gemaakt tussen de actions in de base CRUD controller en een bijbehorende API action. Er moeten dan nog wel handlers geregistreerd worden voor deze API actions door middel van een Collector (zie verderop).
Custom handlers
Een custom handler is een IApiActionHandler die methods specificeert om een bepaalde API action af te handelen. De naam van de method moet overeenkomen met de naam van de action en moet bool returnen: public bool ViewPersons() { }. De return value van de method is uiteraard of de user geautoriseerd is om deze action uit te voeren.
Een handler method mag parameters hebben die overeenkomen qua naam en type met de parameters van de controller method waarop de API action check geplaatst is. Als er parameters aan de handler method toegevoegd worden, dan moeten deze allemaal gevuld kunnen worden vanuit de arguments aan de controller method. Als dit niet zo is, wordt gekeken of er een overload van deze handler method is waarbij wél alle parameters gevuld kunnen worden. Als er geen enkele handler method is die voldoet aan deze eis, wordt er een exception gegooid. Het is mogelijk om slechts een deel van de controller parameters toe te voegen aan de handler method; niet alle parameters hoeven opgegeven te worden, maar áls er één of meer parameters opgegeven worden, moeten ze allemaal gematcht kunnen worden met de controller action arguments.
Tools
In SUMMIT is een tool ontwikkeld die twee overzichten oplevert:
- alle controller actions met daarbij de API action die gecheckt wordt (of “Missing” indien de API action ontbreekt);
- alle API actions met eraan gekoppelde handlers, permissions en parent API actions.
Implementatie
De implementatie van een autorisatiecheck bestaat uit één of twee stappen, afhankelijk van of er custom logica benodigd is om een autorisatiecheck voor een action te doen. In elk geval zal er een Collector aangemaakt moeten worden (of een bestaande uitgebreid, indien dit domeintechnisch logisch is). Indien er custom handling logica benodigd is, zal er ook een class gemaakt moeten worden die IApiActionHandler implementeert.
// Collects authorization checks for a fictional "Person" entity.
[StaticCollectionBind(typeof(IApiActionCollector))]
public class PersonHandlerCollector : IApiActionCollector
{
public void Collect(IApiActionRegistry registry)
{
/* Scenario: custom handler */
// Register a custom handler for the specified API action(s).
registry.RegisterHandler<PersonHandler>(ApiAction.ViewPersons, ApiAction.CreatePersons, ApiAction.DeletePersons);
/* Scenario: permission checks */
// Register a simple permission check for executing the "ViewPersons" action for a Person.
registry.RegisterPermission<Person>(OSPermission.ViewPersons, ApiAction.ViewPersons);
// Register a permission check for executing the "ViewPersons" action for a Person.
// Also recursively check the parent action "ViewEntities" with the specified parent ID.
registry.RegisterPermissionWithParent<Person>(OSPermission.ViewPersons, ApiAction.ViewPersons,
ApiAction.ViewEntities, p => p.EntityId);
// Same as above, but with multiple parents. The user only needs to be authorized for ONE of the parents.
registry.RegisterPermissionWithParents<Person>(OSPermission.ViewPersons, ApiAction.ViewPersons,
(ApiAction.ViewEntities, p => p.EntityId),
(ApiAction.ViewHumans, p => p.HumanId));
/* Scenario: parent actions */
// Register a parent action for the specified API action. This will directly recursively call the parent action.
registry.RegisterParent<Person>(ApiAction.ViewPersons, ApiAction.ViewEntities, p => p.EntityId);
// Same as above, but with multiple parents. The user only needs to be authorized for ONE of the parents.
registry.RegisterParent<Person>(ApiAction.ViewPersons,
(ApiAction.ViewEntities, p => p.EntityId),
(ApiAction.ViewHumans, p => p.HumanId));
/* Scenario: Application permission checks */
registry.RegisterApplicationPermission(ApplicationPermissionNew.ViewReferenceTables, ApiAction.ViewReferenceTables);
/* Scenario: Application parent actions */
registry.RegisterApplicationPermissionWithParents(ApplicationPermissionNew.ViewReferenceTables, ApiAction.ViewReferenceTables, ApiAction.ViewConfig, ApiAction.ViewConfig);
}
}
public class PersonHandler : IApiActionHandler
{
private readonly IDataAccess _dataAccess;
private readonly IThemisIdentity _identity;
// Inject whichever services you need
public PersonHandler(IDataAccess dataAccess, IProvideApplicationIdentity identityProvider)
{
_dataAccess = dataAccess;
_identity = identityProvider.GetIdentity();
}
// Name must match the name of the API action and return type must be bool
public bool ViewPersons()
{
return true; // Your custom logic goes here
}
// Parameters are injected from the arguments to the controller method
public bool CreatePersons(Person person)
{
// Calling HasPermission is allowed here
return person.CompanyId == 42 && _identity.HasPermission(OSPermission.ViewPersons);
}
// It is possible to create overloads to support multiple parameter scenarios
public bool DeletePersons(long id)
{
return id == 1337;
}
public bool DeletePersons(Person person)
{
return DeletePersons(person.Id);
}
}