Table of Contents

Class QuartzHttpApiOptions

Namespace
Quartz
Assembly
Quartz.AspNetCore.dll

How the HTTP API is served.

public sealed class QuartzHttpApiOptions
Inheritance
QuartzHttpApiOptions
Inherited Members

Remarks

There is one set of these per process, not one per scheduler. The API serves every scheduler in the container through one set of endpoints — a request names the scheduler it is for — so what is configured here describes the endpoints rather than any scheduler. That is why there is no IQuartzBuilder registration: calling services.AddQuartzHttpApi(configure) twice configures the same options twice, and the last callback registered wins for any setting both of them touch.

Properties

ApiPath

The path the API is served under. It is a property of the process, not of a scheduler: every scheduler is reached under this one path.

public string ApiPath { get; set; }

Property Value

string

Remarks

MapQuartzHttpApi(pattern) says the same thing where the endpoints are mapped, which is where the rest of an application's routes are written, and a pattern given there wins over this.

IncludeStackTraceInProblemDetails

Whether a failure's stack trace is included in the problem details returned to the caller.

public bool IncludeStackTraceInProblemDetails { get; set; }

Property Value

bool

Remarks

It also puts the real message of a 500 back in the response body. Both are things to read while developing and neither is something to ship: a fault's message routinely names the server, the database, the login or the constraint that produced it.

IsJobTypeAllowed

Which job types a request may name. Null — the default — allows every one, which is what every earlier release did.

public Func<string, bool>? IsJobTypeAllowed { get; set; }

Property Value

Func<string, bool>

Remarks

This is the narrowing for the thing SECURITY.md says is not a vulnerability: a caller who passes authorization can schedule any type that implements IJob, and Quartz.Jobs' NativeJob implements it and starts the executable its job data names. An operator who knows which jobs their deployment schedules can say so here, and the surface stops being "any IJob on the probing path".

The predicate is given the job type name, exactly as the request spelled it. Nothing resolves it first, and that is the point: a name arriving over HTTP is data until the side that runs the job loads it, and resolving one here to compare types would be the assembly probe this API exists not to make. So match on the string — a set of names, a namespace prefix — and allow for the fact that one type has more than one spelling: Acme.Jobs.Nightly, Acme.Jobs and the same name with Version, Culture and PublicKeyToken after it are both it. A predicate matching a bare StartsWith on the namespace covers every spelling at once.

A refused name is 403 with problem details naming the type the request asked for and nothing else — it is the caller's own input, so it gives nothing away. The refusal is per request rather than per job, so schedule-jobs stores none of its batch when one job in it names a type that is not allowed.

It is one predicate for the process, like everything else here, so it cannot say that one scheduler may run a type another may not; the request's scheduler name is not passed to it. The dashboard's QuartzDashboardOptions.IsJobTypeAllowed is the same setting for that surface, and the two are configured separately because a deployment can map one of them and not the other.

MaxPageSize

The most items one paged request may return: 1000 by default, and 0 for no limit. A take naming a number above it is a 400.

public int MaxPageSize { get; set; }

Property Value

int

Remarks

A page size is the only thing on this API a caller can use to make the server do arbitrary work, and until 4.0.0-beta.1 nothing bounded it: one request could materialize every trigger in the store while the bulk key fetch next door refused 1001 keys. The default is that same 1000.

?take=all is bounded by this rather than refused by it. It does not name a number — it says "as many as you will give me" — so it is answered with this many, and hasMore says whether that was all of them. A listing whose matches fit under the cap therefore answers exactly as it would with no cap at all, which is what keeps the 3.x-compatible listings (GetJobKeys and its neighbours) working through HttpScheduler: they ask for everything whether the answer is three rows or three million.

Set it to 0 where an export or a migration really has to take everything in one call, and put the API behind something that says who may.

SchedulerAuthorizationPolicy

The authorization policy every route that names a scheduler is held to, evaluated against a SchedulerResource carrying that name. Null — the default — leaves the API as it was: whatever RequireAuthorization(…) the application put on the mapped group, applied uniformly to every scheduler.

public string? SchedulerAuthorizationPolicy { get; set; }

Property Value

string

Remarks

Set it and each request is checked with IAuthorizationService.AuthorizeAsync(user, new SchedulerResource(name), policy) before the scheduler is looked up, so a caller who fails cannot tell "no such scheduler" from "not yours": a refusal is 403 with problem details, and a 404 only ever answers a scheduler the caller was allowed to ask about. The scheduler listing is filtered the same way.

The check is authorization, never authentication: an anonymous caller gets whatever the policy says, which is a 403 when the policy refuses. Put RequireAuthorization() on the mapped group as well to have an anonymous caller challenged with a 401 first.