Question on behalf of: @rainer-brave-cloud:
Hello,
I am implementing a scheduled (@Scheduled) method in the Data Services Server Application of our project which should work with A12 documents (like querying documents using the DocumentQueryService, update documents and so on).
Since it is scheduled, server-triggered code, there is no user request, thus no security context for checking permissions, so e.g. querying and loading documents fails.
What is the proposed approach to deal with this?
Hi @rainer-brave-cloud,
DS also provides lower-level APIs which do not execute security IDocumentRepository, SearchService, GenericModelReadRepository, DocumentModelReadRepository, RelationshipModelReadRepository … please use those instead of services, loaders and persisters.
Additionally, DS is using quarts for scheduled operations because of better configurability and cluster support. If this is of your interest please consider Quartz implementation instead.
If you decide to run your scheduled code in the standalone operation you can consider also using UAASecurityBypass but this will require some consulting from UAA because the bypass is disabled after the spring context is initialized.
This response is valid for DS 32.2. - 34.2.0
Hey,
The DataService Security is based on the springframework.
Usually I would advise projects to solve this by creating a Service Account and running the sheduled method using the service account.
To run as a certain user you will need a SecurityByPass that sets the correct Authentication Object.
I provided an example Implementation below.
The Implementation below ensures that the Security Context is keept valid in any situation.
However this might not be suitable fit for any case so I advise to consult with SP if you are not sure if this it is secure enough for your situation.
I would strongly advise to not use this in any Places that can be invoked from remote i.E. http endpoints or operations.
import org.apache.commons.lang3.exception.ContextedRuntimeException;
import org.slf4j.Logger;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
@Component
public class SecurityByPass {
private static final Logger LOG = org.slf4j.LoggerFactory.getLogger(SecurityByPass.class);
private final Authentication authentication;
public static SecurityByPass fromUser(String username) {
return new SecurityByPass(new UsernamePasswordAuthenticationToken(username, null));
}
public SecurityByPass(Authentication authentication) {
this.authentication = authentication;
}
public void sudoExec(Runnable runner) {
this.sudo((Supplier<Void>) () -> {
runner.run();
return null;
});
}
public <T> T sudo(RunWithReturnAndException<T> runner) {
LOG.info("Enter Admin User Security Bypass");
SecurityContext context = SecurityContextHolder.getContext();
Authentication prevAuth = context.getAuthentication();
context.setAuthentication(authentication);
try {
return runner.run();
} catch (Throwable e) {
LOG.error("Error while running in Security ByPass", e);
throw createContextedRuntimeException(e);
} finally {
context.setAuthentication(prevAuth);
LOG.info("Exit Security Bypass, restoring previous Authentication");
}
}
@FunctionalInterface
public interface RunWithReturnAndException<T> {
T run() throws Throwable;
}
private ContextedRuntimeException createContextedRuntimeException(Throwable cause) {
ContextedRuntimeException contextedException = new ContextedRuntimeException(cause);
contextedException.addContextValue("Cause", cause);
contextedException.addContextValue("Message", cause.getMessage());
return contextedException;
}
}
Useage Example:
// user is nobody
SecurityByPass.fromUser("sheduled-service-user").sudoExec(() -> {
// user is sheduled-service-user
// do your stuff here and return void
});
// user is nobody
var result = SecurityByPass.fromUser("sheduled-service-user").sudo(() -> {
// user is sheduled-service-user
// do your stuff here and return anything
// if you throw an Exception in here the correct context will be kept and the security context will be restored before the exception will bubble up
});
// user is nobody
Hi tbajus and dboden,
I will try a similar approach as dboden. As I’ve read in this
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-invoking-a-secured-method-from-a-scheduled-job/
there is a way to configure the SecurityContext for scheduled tasks by implementing a SchedulingConfigurer
@Configuration
@EnableScheduling
public class ScheduledJobSecurityConfiguration implements SchedulingConfigurer {
@Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean
public Executor taskExecutor() {
final ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();
final SecurityContext schedulerContext = createSchedulerSecurityContext();
return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);
}
private SecurityContext createSchedulerSecurityContext() {
final SecurityContext context = SecurityContextHolder.createEmptyContext();
final Authentication authentication = new UsernamePasswordAuthenticationToken(
"admin",
"admin",
List.of() // here the required authorities have to be added
);
context.setAuthentication(authentication);
return context;
}
}
The advantage is that your @Scheduled method is not spoiled with security code.
My solution is not working yet because I am still struggeling with configuring an almighty principal 
Thank you,
Rainer.
Good Morning, after rethinking everything, I’ve finally decided to follow tbajus’ advice to use non-secured layers of DataServices. The only concern I see here is that we will rely on packages whose name contains internal, sometimes 
For instance, I cannot autowire the public IDocumentReadRepository because Spring finds two implementations (DefaultRDocumentRepository and CddDocumentRepository. We have to autowire an internal implementation directly.
And finally, I had to provide a fake SecurityContext as well for IDocumentRepository::update method, because for saving, the repository wants to store the user name who triggered the update. So, the repository, although not checking permissions, accesses the SecurityContext.
Hi @rainer-brave-cloud ,
There is a ticket A12S-3004 already created to make DocumentReadRepository public. This will happen in version 35.0.0 so if you are using release line 34.2.0 please @Autowire DocumentReadRepository readRepository rather than DefaultReadDocumentRepository.
We are sorry for the problems with the security context. I have created a ticket to fix that: A12S-3012 we will also fix that in 35.0.0