web-dev-qa-db-fra.com

Passer le paramètre dans la protection de la route

Je travaille sur une application qui comporte de nombreux rôles et dont j'ai besoin pour utiliser des protecteurs afin d'empêcher la navigation vers des parties de l'application basées sur ces rôles. Je me rends compte que je peux créer des classes de garde individuelles pour chaque rôle, mais préférerais avoir une classe dans laquelle je pourrais en quelque sorte passer un paramètre. En d'autres termes, j'aimerais pouvoir faire quelque chose de similaire à ceci:

{ 
  path: 'super-user-stuff', 
  component: SuperUserStuffComponent,
  canActivate: [RoleGuard.forRole('superUser')]
}

Mais puisque tout ce que vous passez est le nom du type de votre garde, vous ne pouvez imaginer un moyen de le faire. Devrais-je simplement mordre la balle et écrire les classes de garde individuelles pour chaque rôle et briser mon illusion d'élégance d'avoir un seul type paramétré?

51
Brian Noyes

Tu dois faire ça.

au lieu d'utiliser forRole(), vous devriez utiliser ceci: 

{ 
 path: 'super-user-stuff', 
 component: SuperUserStuffComponent,
 canActivate: RoleGuard,
 data: {roles: ['SuperAdmin', ...]}
}

et l'utiliser dans votre RoleGuard

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
    : Observable<boolean> | Promise<boolean> | boolean  {

    let roles = route.data["roles"] as Array<string>;
    ...
}
103
Hasan Beheshti

Voici mon point de vue sur ceci et sur une solution possible au problème de fournisseur manquant.

Dans mon cas, nous avons un gardien qui prend une permission ou une liste d’autorisations comme paramètre, mais c’est la même chose qui a un rôle.

Nous avons un cours pour traiter avec les gardes d'autor avec ou sans permission:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Cela concerne la vérification de la session active de l'utilisateur, etc.

Il contient également une méthode permettant d’obtenir une protection personnalisée, qui dépend en fait de la variable AuthGuardService elle-même.

static forPermissions(permissions: string | string[]) {
    @Injectable()
    class AuthGuardServiceWithPermissions {
      constructor(private authGuardService: AuthGuardService) { } // uses the parent class instance actually, but could in theory take any other deps

      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        // checks typical activation (auth) + custom permissions
        return this.authGuardService.canActivate(route, state) && this.checkPermissions();
      }

      checkPermissions() {
        const user = ... // get the current user
        // checks the given permissions with the current user 
        return user.hasPermissions(permissions);
      }
    }

    AuthGuardService.guards.Push(AuthGuardServiceWithPermissions);
    return AuthGuardServiceWithPermissions;
  }

Cela nous permet d’utiliser cette méthode pour enregistrer des gardes personnalisés en fonction du paramètre d’autorisations de notre module de routage:

....
{ path: 'something', 
  component: SomeComponent, 
  canActivate: [ AuthGuardService.forPermissions('permission1', 'permission2') ] },

La partie intéressante de forPermission est AuthGuardService.guards.Push - cela permet de s'assurer que chaque fois que forPermissions est appelé pour obtenir une classe de protection personnalisée, il la stockera également dans ce tableau. Ceci est également statique sur la classe principale:

public static guards = [ ]; 

Nous pouvons ensuite utiliser ce tableau pour enregistrer tous les gardes. C’est acceptable tant que nous nous assurons qu’au moment où le module de l’application enregistre ces fournisseurs, les itinéraires ont été définis et toutes les classes de gardes ont été créées garder ces fournisseurs aussi bas que possible dans la liste - avoir un module de routage aide):

providers: [
    // ...
    AuthGuardService,
    ...AuthGuardService.guards,
]

J'espère que cela t'aides.

4
Ovidiu Dolha

La solution de @ AluanHaddad donne l'erreur "aucun fournisseur". Voici un correctif pour ça (c'est sale, mais il me manque les compétences pour en faire un meilleur).

Sur le plan conceptuel, j'enregistre en tant que fournisseur chaque classe générée dynamiquement créée par roleGuard.

Donc pour chaque rôle vérifié:

canActivate: [roleGuard('foo')]

tu aurais dû:

providers: [roleGuard('foo')]

Cependant, la solution de @ AluanHaddad en l'état générera une nouvelle classe pour chaque appel à roleGuard, même si le paramètre roles est identique. En utilisant lodash.memoize cela ressemble à ceci:

export var roleGuard = _.memoize(function forRole(...roles: string[]): Type<CanActivate> {
    return class AuthGuard implements CanActivate {
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
            Observable<boolean>
            | Promise<boolean>
            | boolean {
            console.log(`checking access for ${roles.join(', ')}.`);
            return true;
        }
    }
});

Notez que chaque combinaison de rôles génère une nouvelle classe. Vous devez donc vous enregistrer en tant que fournisseur every combinaison de rôles. C'est à dire. si tu as:

canActivate: [roleGuard('foo')] et canActivate: [roleGuard('foo', 'bar')] vous devrez enregistrer les deux: providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Une meilleure solution consisterait à enregistrer automatiquement les fournisseurs dans une collection globale de fournisseurs dans roleGuard, mais comme je l’ai dit plus haut, je n’ai pas les compétences pour mettre en œuvre cela.

0
THX-1138