Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PreQuery event is not supported #2379

Open
funnyleolee opened this issue Jul 17, 2023 · 13 comments
Open

PreQuery event is not supported #2379

funnyleolee opened this issue Jul 17, 2023 · 13 comments

Comments

@funnyleolee
Copy link

Feature description

Unable to use the preQuery event to set public parameters before the query.

@dstepanov
Copy link
Contributor

Can you please describe your use-case? What do you mean by public parameters?

@funnyleolee
Copy link
Author

funnyleolee commented Jul 18, 2023

All models belong to an organization, and the data is isolated by the orgId.The sample code snippet like this:

AuthenticationProvider implementation:

public Publisher<AuthenticationResponse> authenticate(Object httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
        return Flux.create(emitter -> {
            String username = (String) authenticationRequest.getIdentity();
            String password = (String) authenticationRequest.getSecret();
            UserEntity user = userService.getByUsername(username).block();
            if (user == null) {
                log.info("User {} not found, login failed!", username);
                emitter.error(AuthenticationResponse.exception(USER_NOT_FOUND));
            } else if (!DigestUtil.bcryptCheck(password, user.getPassword())) {
                log.info("User {} credentials don't match, login failed!", username);
                emitter.error(AuthenticationResponse.exception(CREDENTIALS_DO_NOT_MATCH));
            } else {
                Set<String> roles = user.getRoles().stream().map(RoleEntity::getCode).collect(Collectors.toSet());
                Map<String, Object> attr = new HashMap<>();
                attr.put("org", user.getOrgId());
                emitter.next(AuthenticationResponse.success((String) authenticationRequest.getIdentity(), roles, attr));
                emitter.complete();
            }
        }, FluxSink.OverflowStrategy.ERROR);
    }

Data Entity:

@Where("@.org_id=:orgId")
public class BrandEntity {
    ...   
    private String orgId;
    ...
}

Expected PreQuery code:

import io.micronaut.security.utils.SecurityService;
...
@Inject
private SecurityService securityService;
...
if (securityService.isAuthenticated()) {
    String orgId = (String)securityService.getAuthentication()
            .get()
            .getAttributes().get("org");
    // TODO  set orgId as global data query params
}

If it can be implemented, all query requests do not need to explicitly set orgId.This approach is useful and efficient for user or organizational data isolation

@dstepanov
Copy link
Contributor

Thanks, I'm planning to support external properties

@funnyleolee
Copy link
Author

@dstepanov Is this feature already on the development agenda?

@dstepanov
Copy link
Contributor

You can try put @ParameterExpression(name = "orgId", expression = "#{orgId}") and @AnnotationExpressionContext(OrgIdContext.class) on the repository of your entity and

@Singleton
class OrgIdContext {
...
public getOrgId() { ... }

}

@funnyleolee
Copy link
Author

You can try put @ParameterExpression(name = "orgId", expression = "#{orgId}") and @AnnotationExpressionContext(OrgIdContext.class) on the repository of your entity and

@Singleton
class OrgIdContext {
...
public getOrgId() { ... }

}

let me try. How to make orgId as a global parameter effective in findAll, findById, findOne etc.

@dstepanov
Copy link
Contributor

ParameterExpression should map the @Where("@.org_id=:orgId") parameter to the context one

@funnyleolee
Copy link
Author

That has problem with ExpressionContext. In ExpressionContext class getAuthentication result was empty. But authentication can be obtained in the controller.

@Inject
private SecurityService securityService;
...
 public String getOrgId() {
       
// securityService.getAuthentication() is empty
return securityService.getAuthentication()
               .map(authentication -> authentication.getAttributes().get("ORG_ID").toString())
               .orElse("");
}
...

@dstepanov
Copy link
Contributor

What is your stack?

@funnyleolee
Copy link
Author

micronautVersion=4.4.2

Controller method:

@Slf4j
@Secured(SecurityRule.IS_AUTHENTICATED)
@Controller("/api/brands")
public class BrandController {
  @Inject
  private BrandRepository brandRepository;
  
  @Inject
  SecurityService securityService;
  ...
  @Get("{id}")
  public Mono<BrandEntity> detail(@PathVariable Long id) {
      Optional<Authentication> optional = securityService.getAuthentication();
      log.info("Get authentication ===>>> {}", optional.isPresent()); // It's true
      return brandRepository.findById(id);
  }
...

Repository :

@R2dbcRepository(dialect = Dialect.MYSQL)
@ParameterExpression(name = "orgId", expression = "#{orgId}")
public interface BrandRepository extends ReactorPageableRepository<BrandEntity, Long> {
...
}

Entity:

@Data
@MappedEntity("t_brand")
@Where("@.org_id = :orgId")
public class BrandEntity {
...
}

CustomEvaluationContext (Register using ExpressionEvaluationContextRegistrar):

@Singleton
public class CustomEvaluationContext {

  @Inject
  private SecurityService securityService;

  public String getOrgId() {
      // securityService.getAuthentication().isPresent() is false
     return securityService.getAuthentication()
             .map(authentication -> authentication.getAttributes().get("ORG_ID").toString())
             .orElse("");
  }
}

@funnyleolee
Copy link
Author

If PreQuery can be support, Use like PreUpdate, PreDelete etc... This will be much more elegant than the Expression Evaluation Context approach

@dstepanov
Copy link
Contributor

There are some fixes in #2876 that should correct context propagation.

How would you imagine for PreQuery to work?

@funnyleolee
Copy link
Author

Scenario of a single database we use. And org_id or tenant_id column is used to logically isolate data.Planned to use the prequery listener to uniformly add global parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants