API - Come gestire la funzionalità basata sull'ambito?

2

TLDR;

Where and possibly how should I implement scope based logic in the example code?

Ho un API Web ASP.NET.

L'API utilizza OData (in cima al REST) per endpoint dati e autenticazione OAuth 2.0. Ora voglio aggiungere funzionalità per gestire un token di accesso in diversi ambiti.

Alcuni degli ambiti sono:

  • utente
  • Organizzazione
  • Clienti

Il risultato impostato sui ritorni API dovrebbe essere basato su questo ambito. Lo stesso vale se qualcuno vuole aggiungere, aggiornare o eliminare un'entità.

Ora prendi ad esempio il seguente endpoint:

http://myapi/Employees

Qualcuno con un token nell'ambito Organizzazione fa un GET e dovrebbe solo ottenere i dipendenti che si trovano nell'organizzazione che è definita all'interno del token. Un utente nell'ambito Cliente dovrebbe ottenere i dipendenti per tutte le organizzazioni all'interno del cliente definito nel token. Questa funzionalità tuttavia non è disponibile per qualcuno con un token nell'ambito Utente.

Lo stesso vale per un POST. A qualcuno all'interno dello spazio dell'organizzazione dovrebbe essere consentito aggiungere un nuovo dipendente alla propria organizzazione.

La funzionalità per i diversi ambiti di solito differisce solo un po '. Nel caso di ottenere i dipendenti la differenza è solo un punto sull'organizzazione o altrimenti sul cliente. Nel caso di un post, dovrebbe essere un controllo per verificare se, per esempio, l'organizzazione è parte del cliente che è vincolato al token del cliente.

Implementazione corrente:

Attualmente abbiamo un ApiController di base che è tipizzato generico. Il controller di base chiama una Query o CommandHandler con il tipo di entità e tutti i dettagli di implementazione si trovano all'interno di questi gestori.

ApiController

public abstract class ApiController<TEntity> : EntitySetController<TEntity, Guid>
    where TEntity : class, IApiEntity
{
    protected readonly IQueryProcessor QueryProcessor;
    protected readonly ICommandProcessor CommandProcessor;

    protected ApiController(IQueryProcessor queryProcessor, ICommandProcessor commandProcessor)
    {
        this.QueryProcessor = queryProcessor;
        this.CommandProcessor = commandProcessor;
    }

    [HttpGet]      
    public override IQueryable<TEntity> Get()
    {
        var command = new GetEntityCommand<TEntity>();
        return this.QueryProcessor.Process(command);
    }
}

EmployeeController

public class EmployeeController : ApiController<Employee>
{ }

GetEmployeeHandler

public class GetEmployeeCommandHandler : IQueryHandler<GetEntityCommand<Employee>, IQueryable<Employee>>
{
    public IQueryable<Employee> Handle(GetEntityCommand<Employee> command)
    {    
         // Code that knows how to get a collection of employees.
         // This collection contains all employees
    }
}   

PostEmployeeHandler

public class PostEmployeeCommandHandler : ICommandHandler<PostEntityCommand<Employee>, Employee>
{
   public Employee Handle(PostEntityCommand<Employee> command)
    {    
         // Code that knows how to add an employee to the database.
    }
} 

La domanda

Dove e possibilmente come dovrei implementare la logica basata sull'oscilloscopio? So che ci deve essere un modo intelligente per farlo e un buon posto per implementare questa logica basata sull'oscillazione, io proprio non la vedo (ancora).

    
posta Jos Vinke 25.07.2014 - 21:44
fonte

3 risposte

1

Suggerisco caldamente di inserire questa logica dell'oscilloscopio all'interno della progettazione dello schema di dati (ad esempio tabelle SQL). Non filtrare. Il motivo è principalmente la scalabilità.

  • Quando ci sono così tanti elementi di dati, non si vuole chiedere al DB di caricarli tutti e quindi si applica la scoping su questi elementi in memoria. Sovraccarichi il DB con lavoro extra e anche con la memoria.
  • Il database può interrogare gli elementi con i parametri dell'ambito (gli ottimizzatori di query funzionano molto bene per SQL) molto meglio. Avrai una velocità migliore.
  • L'uso del DB ti aiuterà anche a evitare molti problemi con le transazioni e la concorrenza.
risposta data 27.07.2014 - 16:46
fonte
0

Penso che almeno una parte della risposta sia quella di rendere il tuo token di accesso "ambient", in modo che tu possa accedervi da qualsiasi parte del tuo codice senza doverlo passare come argomento ad ogni chiamata.

Uno dei modi per farlo è definire un IPrincipal personalizzato che includa il token di accesso e impostarlo su HttpContext.Current.User *, ad es. vedi questa risposta. In MVC, di solito c'è un filtro che ti consente di collegarti nel punto corretto.
* Se lo fai abbastanza presto, l'entità viene copiata automaticamente in Thread.CurrentPrincipal , altrimenti potresti doverla impostare manualmente.

Una volta installato, è possibile utilizzare i metodi di estensione per eseguire l'ambito. Ad esempio, qualcosa di simile:

public static IEnumerable<Employee> FilterToScope(this IEnumerable<Employee> employees)
{
    var token = (Thread.CurrentPrincipal as MyTokenPrincipal).Token;
    if(token.IsCustomerScoped)
    {
        //Filter employees on customer
    } else if //etc.
}
    
risposta data 27.07.2014 - 16:21
fonte
0

Raccomanderei di non restituire informazioni diverse sullo stesso URI in base all'ambito dell'utente.

È meglio convalidare una serie di parametri che un cliente passa al servizio rispetto agli obiettivi autorizzati. REST identifica una risorsa o risorse tramite il suo URI. e mi sembra contrario ai principi REST per restituire diversi set di informazioni tramite lo stesso URI.

Quindi, nel tuo codice assicurati che il client che richiede http://myapi/Employees/?organization=xyz abbia scope organization_xyz prima di restituire qualcosa a lui.

    
risposta data 31.01.2015 - 21:48
fonte