Подтвердить что ты не робот

Пропустить параметр в охранник маршрута

Я работаю над приложением, у которого есть много ролей, которые мне нужно использовать для защиты от вторжения в части приложения на основе этих ролей. Я понимаю, что могу создавать индивидуальные классы защиты для каждой роли, но предпочел бы иметь один класс, который мог бы каким-то образом передать параметр. Другими словами, я хотел бы сделать что-то похожее на это:

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

Но поскольку все, что вы проходите, является типом имени вашего охранника, не может придумать, как это сделать. Должен ли я просто пропустить пулю и написать отдельные классы защиты для каждой роли и разрушить мою иллюзию элегантности при использовании только одного параметризованного типа?

4b9b3361

Ответ 1

Вы должны сделать это.

Вместо использования forRole(), вы должны использовать это:

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

и использовать это в своем RoleGuard

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

    let roles = route.data.roles as Array<string>;
    ...
}

Ответ 2

Здесь я беру на себя это и возможное решение проблемы с отсутствующим провайдером.

В моем случае у нас есть защитник, который принимает разрешение или список разрешений как параметр, но он имеет то же самое, что и роль.

У нас есть класс для работы с auth-охранниками с разрешения или без разрешения:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Это относится к проверке активного сеанса пользователя и т.д.

Он также содержит метод, используемый для получения специального защитного ограждения, который фактически зависит от самого AuthGuardService

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;
  }

Это позволяет нам использовать метод для регистрации некоторых настраиваемых защит на основе параметра разрешений в нашем модуле маршрутизации:

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

Интересной частью forPermission является AuthGuardService.guards.push - это в основном гарантирует, что любое время forPermissions вызывается для получения пользовательского класса защиты, он также сохранит его в этом массиве. Это также статично для основного класса:

public static guards = [ ]; 

Затем мы можем использовать этот массив для регистрации всех охранников - это нормально, если мы убеждаемся, что к моменту, когда модуль приложения зарегистрирует этих поставщиков, маршруты были определены и созданы все классы защиты (например, проверьте импорт и сохранить этих поставщиков как можно ниже в списке - с помощью модуля маршрутизации):

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

Надеюсь, что это поможет.

Ответ 3

Решение @AluanHaddad дает ошибку "нет провайдера". Вот за что это исправить (он чувствует себя грязным, но мне не хватает навыков, чтобы сделать лучше).

Концептуально я регистрирую в качестве поставщика каждый динамически созданный класс, созданный roleGuard.

Итак, для каждой проверенной роли:

canActivate: [roleGuard('foo')]

вы должны иметь:

providers: [roleGuard('foo')]

Однако, решение @AluanHaddad as-is будет генерировать новый класс для каждого вызова roleGuard, даже если параметр roles тот же. Используя lodash.memoize, он выглядит так:

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;
        }
    }
});

Обратите внимание: каждая комбинация ролей генерирует новый класс, поэтому вам необходимо регистрировать в качестве поставщика каждую комбинацию ролей. То есть если у вас есть:

canActivate: [roleGuard('foo')] и canActivate: [roleGuard('foo', 'bar')] вам нужно будет зарегистрировать оба: providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Лучшим решением будет регистрация поставщиков автоматически в глобальной коллекции поставщиков внутри roleGuard, но, как я уже сказал, мне не хватает навыков для ее реализации.