Goal
I am working in a module similar to SpringActuator (I need /health, /info, /metrics, etc), I need the module to be reused across several apps, but the logic to determine if the app is healthy or to get the app info is defined at the application itself, I want this actuator module to be agnostic of the application. So I need the application to import the module passing a provider that exists already at the application context.
Implementation
I used the ModuleRef (with strict option) to create something like a ServiceLocator, so that any app dependency could be resolved during module initialization.
The actuator module is imported like this
imports: [ActuatorModule.forRoot({ actuatorToken: ApplicationActuator })]
The ApplicationActuator is the class knowing if the app is healthy or not. This provider could have another dependencies internally (nested)
The ActuatorModule uses a factory approach to provide the service locator(FactoryHelper)
providers: [
{
provide: 'ActuatorFactoryHelper',
useFactory: (moduleRef: ModuleRef) => new FactoryHelper(moduleRef, options.actuatorToken),
inject: [ModuleRef],
}
]
And finally my service uses the FactoryHelper like this
get actuator(): Actuator {
return this.factoryHelper.instance;
}
constructor(#Inject('ActuatorFactoryHelper') private readonly factoryHelper: FactoryHelper<Actuator>) {}
This is how the FactoryHelper looks like
export class FactoryHelper<T> implements OnModuleInit {
instance: T;
constructor(
private readonly moduleRef: ModuleRef,
private readonly token: Type<any> | string | symbol,
) {}
onModuleInit() {
this.instance = this.moduleRef.get(this.token, { strict: false
});
}
}
Question
I read in another threads that having a module depending on a provider is a bad practice, Is it a bad practice? what issues could I face? Is it a easier way of doing it?
Notes
I tried using custom providers (useFactory) but nested dependencies got not resolved (they are not visible to the actuator module).
Thanks in advance.
Related
I am pretty new to developing REST APIs using the NestJS framework (started using it last week).
For Authorization purposes I want to implement a CanActivate Guard to my app. This guard simply looks for an authorization header to extract the jwt. Furthermore the user needs to be fetched via a service to get its role and check for the required permission. Additionally there is a #Permission decorator, which is used on protected routes, that takes in a permission string.
This decorator file looks like this:
export const PERMISSION_KEY = "requiredPermission"
export const Permission = (permission: string) => {
return SetMetadata(PERMISSION_KEY, permission);
}
But I am experiencing strange behaviour: Only injecting the Reflector (so I can look up the required permission from the route) is working fine. When now trying to inject a service, let's say AuthService, the constructor of the AuthenticationGuard isn't even called, so the service results in undefined. Event the reflector instance is undefined, though it worked before. Here is how it looks in my authentication.guard.ts:
#Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(
private reflector: Reflector,
private authService: AuthService
) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
// ... do some things to extract jwt from header ...
console.log(this.authService); // Results in undefined
// This line does not work because reflector also is undefined.
const requiredPermission = this.reflector.get<string>(PERMISSION_KEY, context.getHandler());
console.log(requiredPermission);
}
}
This guard is imported as a provider in a feature module called AuthModule:
#Module({
imports: [
JwtModule.register({
secret: "EXTREMELY_SECRET"
})
],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthenticationGuard
}
],
controllers: [ AuthController ],
exports: [
AuthService
]
})
export class AuthModule {}
Now when removing the service from dependency injection and make a console.log, I can see that it gets instantiated as the first dependency of the whole app. Does this maybe cause the service to fail injecting? How would I possibly change fix that?
Or does injecting services not work for guards in general? I think this is not the problem.
Maybe someone can help me with this problem and give me some advice on to fix this issue.
Thank you very kindly in advance for your support!
I am building an authorization service for my nestjs app.
For every protected resource on my app (Media, Game, ...), I have a *RoleEntity associated (MediaRoleEntity, GameRoleEntity, ...) that defines what a user can do with a specific resource. Each one of this *RoleEntity implements RoleEntityInterface:
export interface RoleEntityInterface<T extends ResourceEntity> {
resource: T;
user: UserEntity;
role: string;
}
Each protected entity (MediaEntity, GameEntity, ...) extends ResourceEntity.
Now I want to build a generic provider RoleService, responsible for database interaction:
#Injectable()
export class RoleService<T extends ResourceEntity> {
readonly roleRepository: Repository<RoleEntityInterface<T>>;
async read(roleDto: Partial<RoleDto<T>>): Promise<RoleEntityInterface<T>> {
return this.roleRepository.findOne({
where: { ...roleDto },
});
}
async create(roleDto: RoleDto<T> | RoleDto<T>[]): Promise<void> {
await this.roleRepository.insert(roleDto);
}
}
And I want inject this service in guards, interceptors...
Problem, I don't know how to do that, more precisely:
How can I dynamically inject the roleRepository ? (I imagine some kind of factory has to be involved.)
REAL USE CASE
I want to be able to protect resources with a guard:
#Injectable()
export class RoleGuard<T extends ResourceEntity> implements CanActivate {
constructor(
private authService: AuthService,
private roleService: RoleService<T>,
private readonly reflector: Reflector,
) {}
...
}
Now in a controller, when I use
#Role('Admin')
#UseGuards(RoleGuard<MediaEntity>)
Get()
...
It would be perfect if the whole thing magically works :), ie correct roleService with correct roleRepository are properly injected.
I am completely new to nestjs and typescript (and never played with angular neither) so maybe the whole approach is wrong..
I am trying to wire up a basic Angular2 app that uses the Http service. (Most of the tutorials I've seen do this by having a Component consume the Http service, which seems wrong unless the basic philosophy of thin controllers has changed – but that's a different question.)
I would like to create a service that uses Angular's Http service. But I can't figure out how to inject the Http service other than this:
boot.ts:
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {HTTP_PROVIDERS } from 'angular2/http';
bootstrap(AppComponent, [HTTP_PROVIDERS]);
myService.ts:
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class aService{
constructor(http:Http){
}
/** do some stuff *//
}
This works, but it seem very wrong to require the user of the service to know the service's dependencies and be required to inject them into the bootstrap process. It seems like there should be a way to directly hand a providers array to a service the same way you can a component, but I can't find it. Am I just missing something?
Update
This way if a parent injector provides an implementation for OtherService this one is used, otherwise OtherServiceImpl is used (default).
#Injectable()
class SomeService {
OtherService _other;
SomeService(Injector injector) {
_other = injector.getOptional(OtherService);
if (_other == null) {
_other = injector.resolveAndCreateChild([
provide(OtherService, useClass: OtherServiceImpl)
]).get(OtherService);
}
_other.doSomething();
}
}
If you provide another one like
bootstrap(AppElement, [
provide(OtherService, useClass: OtherServiceImpl2)
]);
OtherServiceImpl2 is used.
See also https://github.com/angular/angular/issues/5622
Original
You could just make the http service optional (using the #Optional() annotation) and if none is provided just create an instance inside the constructor with new Http().
This way the user doesn't need to know about the services dependencies, but is able to pass alternative implementations if necessary (for example for testing).
If creating the dependeny inside the service requires DI itself, you can inject an injector and use it to get dependencies.
See also optional dependencies in http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
What also could work (not tried myself yet) is just to create a child injector and instruct it to skip self
From the SkipSelfMetadata documentation
class Dependency {
}
#Injectable()
class NeedsDependency {
dependency;
constructor(#SkipSelf() dependency:Dependency) {
this.dependency = dependency;
}
}
var parent = Injector.resolveAndCreate([Dependency]);
var child = parent.resolveAndCreateChild([NeedsDependency]);
expect(child.get(NeedsDependency).dependency instanceof Depedency).toBe(true);
var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
expect(() => inj.get(NeedsDependency)).toThrowError();
I don't know yet if this still resolves from "self" if parent can't provide the requested type.
I'm trying to secure endpoints Actuators inside Spring Boot project. However instead using ready-to-run Spring Security configuration for Actuators:
management:
security:
enabled: true
role: ADMINISTRATOR
That too easy I need to plug Actuators with our custom security (here CAS SSO).
First try it was to add context-path for Actuators:
management:
security:
enabled: true
role: ADMINISTRATOR
context-path: /management
And update my WebSecurityConfigurerAdapter configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.authorizeRequests()..antMatchers("/management/**").hasRole(Role.ADMINISTRATOR.toString());
...
}
It works but I must hardcode Actuators context-path, so when I want to update management.context-path I have to update my security.
I know it's possible to retrieve value of management.context-path but how to manage it when value equals ""?
You can answer me to #Autowired EndpointHandlerMapping and retrieve list of Actuators endpoints... Finally I will copy-past same logic as ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter.
Furthermore ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter #ConditionalOnMissingBean is pointing itself but ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter is inner-static protected class so not possible to disable it without passing parameter management.security.enabled=false and that can be strange because your configuration said management.security.enabled=false but in reality endpoints are secured...
Conclusion
Is there a way to override (just a part of) properly Actuators security
May I miss something and I'm totally wrong?
There is already a pending Issue on Github. For the moment Dave Syer proposes:
I think copy-paste of all the code in there is actually the best
solution for now (and set management.security.enabled=false to let
Boot know you want to do it yourself).
I have not tested whether a runtime exception will be thrown but I think that you can reuse ManagementWebSecurityConfigurerAdapter and save a lot of copy-paste action. At least compiler doesn't complain.
Put your configuration class under package org.springframework.boot.actuate.autoconfigure in your project and extend from ManagementWebSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter. Don't miss all the annotations from ManagementWebSecurityConfigurerAdapter. That is the only copy-paste action here because class annotations can not be inherited by subclass.
package org.springframework.boot.actuate.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
#Configuration
#ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true)
#Order(ManagementServerProperties.BASIC_AUTH_ORDER)
public class SsoManagementWebSecurityConfigurerAdapter extends ManagementWebSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter {
//TODO your SSO configuration
}
Don't forget to #Import your configuration in your #SpringBootApplication.
It is stated in the ZF2 documentation, as well as by Matthew Weier O'Phinney's blog, that:
Many developers want to stick this in their MVC application directly,
in order to have pretty URLs. However, the framework team typically
recommends against this. When serving APIs, you want responses to
return as quickly as possible, and as the servers basically
encapsulate the Front Controller and MVC patterns in their design,
there's no good reason to duplicate processes and add processing
overhead.
It is recommended that you put the server endpoints in the public directory structure. For example, you might have /public/some-api.php that instantiates and runs the Zend RPC Server. But I have already created this dope module in which I have a bunch of classes and a config file that lays out the dependency injection, factories, etc for creating the classes.
Soo... how do I leverage that code in my RPC server, without putting it into a MVC controller?
Thanks!
Adam
Here is how I did it. I have this broken out into a few files, but you can put all this in your public root directory, something like rpc-service.php:
use Zend\ServiceManager\ServiceManager,
Zend\Mvc\Service\ServiceManagerConfig;
class Bootstrap {
/** #var ServiceManager */
private static $serviceManager;
private static function _go() {
chdir(dirname(__DIR__));
require __DIR__ . '/../init_autoloader.php';
$config = include __DIR__ . '/../config/application.config.php';
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $config);
$serviceManager->get('ModuleManager')->loadModules();
self::$serviceManager = $serviceManager;
}
/**
* #return ServiceManager
*/
public static function getServiceManager() {
if (!self::$serviceManager)
self:: _go();
return self::$serviceManager;
}
}
$sm = Bootstrap::getServiceManager();
use Zend\Json\Server\Server,
Zend\Json\Server\Smd,
$jsonRpc = new Server();
$jsonRpc->setClass($sm->get('Some\Class'));
$jsonRpc->getRequest()->setVersion(Server::VERSION_2);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
echo $jsonRpc->getServiceMap()->setEnvelope(Smd::ENV_JSONRPC_2);
}
else {
$jsonRpc->handle();
}
As you can see, I'm using the Service Manager! Yay. All is right in the world.