Dokumentacja AlgoJudge0.1

Permissions

The string form, the two scopes, the resolution rules, the three shipped templates, and all 52 keys.

Ta strona jest po angielsku

Polskie tłumaczenie obejmuje sekcje Instalacja i utrzymanie i Korzystanie z AlgoJudge. Pozostałe sekcje są materiałem technicznym i pozostają po angielsku.

The Server is what enforces authorisation, so the whole vocabulary lives in one place: AlgoJudge.Server/Authorization/Permissions.cs. The Client fetches it rather than hard-coding it, so an installation that adds an entry does not need a Client release to show it.

The form

resource:operation[:qualifier]

A permission is a string, not a column. A schema that enumerated these in columns could not express one that did not exist when the migration was written.

own versus all is baked into the operation rather than evaluated as a separate ownership test. That covers the two cases the product has and is deliberately not general: "everyone in my group" would need a new operation, not a different predicate.

The two scopes

A grant holds a set of these strings and is scoped: system-wide (ActivityId null) or to one activity. The same string means the same thing at either scope; what changes is how far it reaches.

Scope column belowWhere the key is meaningful
globalin a system grant only
activityin an activity grant only
bothin either — in a system grant it means "in every activity"

Resolution

In this order. The order is the decision.

  1. An activity grant carrying OverrideSystem is authoritative inside its activity, and nothing else applies there — not even system:administrator. Checked first, because checking the administrator bypass before it is exactly how the override would be lost.
  2. system:administrator, held in any system contribution, bypasses every other permission check at every other scope. It does not put its holder in an activity, and submitting asks that separately.
  3. Otherwise the effective set is the union: every system contribution, plus the activity grant when one activity is asked about.
  4. There is no subtraction anywhere in the model.
  5. Nobody may grant, or map onto, a permission they do not themselves hold. Enforced where a grant is written, not where it is read.

There is no deny list to reach for

"A manager everywhere, except in this contest where I compete" is OverrideSystem — an activity grant that replaces the system contributions wholesale — and never a permission taken away. A model with denies has to say which of two contributions wins where they disagree, and every answer to that is somebody's surprise.

An Invited grant confers nothing. A grant whose Permissions column does not parse grants nothing either — the alternatives being to lock every user out of every screen, or to treat a corrupt row as an administrator.

System scope is several rows

A user holds one system contribution per source: the manual one, plus one per linked identity provider. They union. This is the one place the model is additive.

Two answers a caller must not conflate

The Server's "which activities is this permission held in" query returns null for not restricted and a list for these activities. An empty list is a third answer and means none.

The three shipped templates

Choosing a template copies its permissions into the grant. Nothing points back afterwards, which is the whole reason it is a template rather than a role: a role that grants keep pointing at is an object somebody has to keep correct for the lifetime of the installation.

The cost is stated plainly: a correction has to be applied to existing grants by hand.

One object does hold a live reference

An identity provider's mapping rule names a template, and the contribution is re-derived from it at every sign-in. Editing a template touches nobody who already used it — except the people whose grants a mapping rule rewrites.

participant — 7 keys

activity:read        submission:read:own    submission:create    result:read:own
question:read:own    question:create        ranking:read

This list is load-bearing beyond the grant editor: it is what the staff test measures against, and therefore what decides who appears in a ranking.

manager — 32 keys

Everything a participant has, plus:

activity:update            activity:archive      activity:enroll
problem:read:own           problem:create        problem:update
problem:share              problem:archive       problem:attach
submission:read:all        submission:source:read:all
submission:rejudge         submission:cancel     submission:exclude
result:read:all            result:log:read:all
question:read:all          question:answer       question:publish
announcement:create
ranking:read:unfrozen      ranking:unfreeze
user:create:temporary
grant:read:all             grant:update

The other twenty are deliberately not included: activity:create, activity:delete, trial:run, problem:read:all, problem:delete, problem:import:external, every user: key beyond temporary accounts, template:read, template:manage, the four runner: keys, instance:update, provider:manage and system:administrator. A manager runs an activity; they do not run the installation it lives in.

admin — 1 key

system:administrator

One entry, because it bypasses the rest. An administrator with a list of individual permissions is an administrator who can be quietly trimmed.

Staff, and the ranking

A grant is systemic — out of the participant count and out of the ranking — when it carries any permission an ordinary participant does not hold. The Server computes that on every write and never accepts it from the caller.

Two flags are kept per catalogue entry rather than one, because they are two questions: does the participant template grant it and does holding it make somebody staff. They differ for exactly one key.

`trial:run` is held without becoming staff

It is outside the participant template, because a trial spends a Runner and opening it is a manager's decision in one activity. It is not staff-conferring, because spending your own time on your own package is not seeing or changing other people's work.

Inferring one flag from the other would have emptied a whole activity's board the moment somebody was allowed to time their own code.

A key the catalogue does not describe counts as staff. An unknown right is more likely to be a new one somebody has been given than an ordinary participant's, and guessing the other way would quietly put them in the ranking.

The catalogue — 52 keys

Activity

PermissionScopeMeans
activity:readactivitysee the activity and its series at all
activity:createglobalcreate activities
activity:updatebothsettings, series, dates, freeze
activity:archivebothclose an activity: still readable, nothing new accepted
activity:deletebothdestroy an activity and everything in it
activity:enrollbothadd and remove members
trial:runbothask for a package to be run without it being anybody's problem

Problems

The library is private by default; these say what a manager may do with a problem they can already see. Which problems those are is decided by ownership and the share list.

PermissionScopeMeans
problem:read:ownglobalsee problems they own or that are shared with them
problem:read:allglobalsee every problem in the installation, sharing ignored
problem:createglobaladd a problem to the library
problem:updateglobaledit statement, package, configuration, publish a version
problem:deleteglobaldestroy a problem, refused while it is attached anywhere
problem:shareglobalchange who else can see it
problem:archiveglobalretire a problem without breaking what uses it
problem:attachbothattach a problem to a series and configure the assignment
problem:import:externalglobalcreate a problem whose slug carries a reserved prefix

The reserved list is configuration, never a literal in the Server — it must not learn the name of any particular archive.

Submissions

PermissionScopeMeans
submission:read:ownactivitysee one's own submissions
submission:read:allbothsee everyone's submissions
submission:createactivitysubmit a solution. Membership is asked separately — see below
submission:source:read:allbothread the source of someone else's submission
submission:rejudgebothqueue another evaluation attempt
submission:cancelbothcancel a queued or running job
submission:excludebothrule that one submission counts towards no standing

submission:source:read:all is separate from submission:read:all on purpose: seeing that someone submitted, and reading what they wrote, are different disclosures. submission:exclude is separate from submission:rejudge because rejudging asks the Runner to look again while excluding decides an outcome by hand.

Holding the key is not being in the activity

submission:create is the first of two questions; the second is membership. Submitting is refused — 403, code enrolment.required — unless the submitter holds an active grant in that activity. Resolution unions permissions, and membership is not a permission: nothing carries it in from anywhere else. Without the second question a submission would be graded and listed among the manager's rows while belonging to nobody on the standings — the ranking builds its contestants from activity grants.

system:administrator is not exempt, and that is the decision here worth arguing with: the bypass is a bypass of permissions, and this is not a permission question. An administrator who wants to compete takes an activity grant, which costs them one request. An Invited grant is not membership either — the resolver discards it, and so does this.

Two things it does not reach. Creating an activity writes its creator an active grant in it, so nobody is shut out of what they just made. And trial:run is a different door: a trial is nobody's submission, so nothing about it asks who is in the activity.

Results

PermissionScopeMeans
result:read:ownactivitysee one's own verdicts and per-test detail
result:read:allbothsee everyone's
result:log:read:allbothread the evaluation log of any submission

The activity's attachment rules govern participants; result:log:read:all governs managers, and is what lets a jury read a log the participants cannot.

Questions and announcements

PermissionScopeMeans
question:read:ownactivitysee one's own questions and every published one
question:read:allactivitysee every question, published or not
question:createactivityask a question
question:answeractivityanswer a question
question:publishactivitymake an answered question visible to everyone
announcement:createactivitypublish an announcement

Ranking

PermissionScopeMeans
ranking:readactivitysee the ranking as published
ranking:read:unfrozenbothsee through a freeze
ranking:unfreezebothlift the freeze for everyone

Users

PermissionScopeMeans
user:read:allglobalsee the account list
user:createglobalcreate an account
user:updateglobaledit an account
user:blockglobalstop an account signing in
user:create:temporarybothcreate temporary accounts in bulk
user:mergeglobalcarry one account's work onto another

user:create:temporary is the one account operation an activity manager needs — a contest hands out accounts at the door — so it is the one that exists at activity scope. user:merge is its own key rather than part of user:update: it hands one person's submissions and points to somebody else, and it should not arrive with ordinary account editing.

Grants and templates

PermissionScopeMeans
grant:read:allbothsee who holds what
grant:updatebothchange what someone holds
template:readglobalsee the templates — and so does grant:update, held anywhere
template:manageglobalcreate, edit and delete templates

grant:update is why "nobody may grant a permission they do not hold" exists. An activity manager needs it to run their own group, and without that rule it is the same as being able to make oneself an administrator.

The template list answers to either key, held anywhere. template:read is global, and a global key in an activity grant confers nothing, so an activity manager could never hold it — while applying a template is exactly what they do when they enrol somebody by hand. So GET /api/v1/permission-templates admits template:read or grant:update from any grant the caller holds, and putting template:read into a manager set of your own is not the fix it looks like. Reading one discloses nothing: a template is copied into a grant, never referenced, and the excess rule still refuses to hand on a permission the caller does not hold.

Runners and the installation

PermissionScopeMeans
runner:readglobalsee registered Runners and their state
runner:approveglobalapprove a self-registered key
runner:revokeglobalrevoke a key
runner:updateglobalrename, retag, add a note
instance:updateglobalwhat the installation is called, the mark it shows, the documents it publishes
provider:manageglobalregister identity providers, edit their claim mapping, enable and disable them
system:administratorglobalbypasses every permission check and cannot be trimmed. Refused outright in an activity grant, and the last one cannot be taken away — see below

provider:manage is the most dangerous key here after system:administrator, because a mapping rule decides what a token buys. Its two guards are described in Identity, and both are enforced rather than documented.

instance:update is not in the manager template: running an activity is not running the installation it lives in.

system:administrator is refused in an activity grant

It is a bypass only in a system-scoped grant. Writing it into an activity grant is refused outright rather than stored and ignored — the code is grant.permission.scope, and the message says the key is installation-wide and means nothing in an activity.

The refusal is narrow on purpose. Several problem:* keys are declared global too and are equally inert in an activity grant, and they are not refused, because the shipped manager template contains them: a rule that refused every global key would refuse the template this installation hands out.

The installation cannot lose its last administrator

Revoking the only active system grant that carries the key is refused. So is rewriting it so that it no longer carries the key, and so is demoting it to invited — the resolver reads active grants only, so an invited administrator administers nothing. All three answer 403 with the code grant.administrator.last, and the message says to grant it to somebody else first.

The question is asked about the row rather than the person: a second administrator anywhere in the installation makes every one of those acts legal again, including on the row that was refused a moment earlier.

The way back is a restart, because aj-admin has no command for grants. Where no active system grant carries the key at all, the Server grants the admin template back to the seeded admin account on start and logs a warning. It never rewrites a system grant somebody made by hand: where that account already holds one that does not administer, the log says so and the row is left alone, so an operator who moved the role onto a named person keeps their arrangement.

A restart returns the rights, not the password. If nobody knows that either, aj-admin password sets one — see Troubleshooting.

Archiving versus deletion

Both follow the same shape: archive is the ordinary act, deletion is the forceful one, and they are separate permissions so a manager can have the first without the second.

  • An archived activity stays readable and accepts nothing new. It keeps its ranking and its history, because that is what participants come back for.
  • An archived problem disappears from the attach picker and takes no new versions, while every assignment already using it keeps working.
  • A problem that is attached anywhere cannot be deleted. The database refuses it on its own, whether or not the service layer remembers to check.

Na tej stronie