User Management
This page describes how Artemis accounts come into existence, how the activated flag governs whether
they may authenticate, and how to configure self-registration.
Account types
Every account is either internal or external, recorded in the is_internal column:
| Internal account | External account | |
|---|---|---|
| Password | Hashed and stored by Artemis | Not stored; the identity provider verifies it |
| Authenticated by | ArtemisInternalAuthenticationProvider | LdapAuthenticationProvider, SAML2Service, or OIDCService |
| Password reset | Through Artemis | Through the institution's own account management |
| Typical use | Admins, service accounts, guests, instances without an IdP | Students and staff of the operating institution |
An account can be switched between the two through the admin user form. Turning an external account internal requires a password, which Artemis generates when the form leaves it blank.
Where accounts come from
Several paths create accounts. The important column is the last one — see
The activated flag for why.
| Provisioning path | Account type | Created activated? |
|---|---|---|
Self-registration, POST /register | Internal | No — awaits activation by the user |
| First login through LDAP | External | Yes |
| First login through SAML2 | External | Yes |
| First login through OIDC | External | Yes |
| Course member import, exam registration, admin user import (resolved against LDAP) | External | Yes |
| LTI launch | Internal | No — activated when the launch hands over the generated password |
| Admin Create User form | As selected | Yes |
internal-admin bootstrap on startup | Internal | Yes |
| Iris bot account | Internal | Yes |
Only the first row leaves the account unactivated, and only that row is a user activating their own account. Every other path provisions the account on someone's behalf, so it must be usable immediately.
Course and exam import and first login can both create the same person's account, whichever happens first. Both must therefore produce an account in the same state — an imported student who has never logged in has to be able to use their repositories straight away.
An LTI launch creates the account unactivated and activates it when it hands the account holder the Artemis password generated for them, so they can also sign in outside the LMS.
Importing users
Instructors add course members and exam participants from a list of identifiers, and administrators can import users the same way. Each row may carry any combination of login, email and registration number.
Resolution happens in two stages. The Artemis database is searched first and completely; only if nothing matches is the configured LDAP consulted, and an account found there is created in Artemis. Within each stage the identifiers are tried in the order login, then email, then registration number, stopping at the first match. Blank identifiers are skipped, and a row with all three blank is reported as not found.
| Outcome | What happens |
|---|---|
| Found in the Artemis database | The existing account is used as it is |
| Found only in the directory | An external, activated account is created and used |
| Found nowhere | The row is returned to the caller as not found and no account is created |
Accounts created this way are activated, exactly like accounts created on first login. This matters: an imported student who has not signed in yet must be able to clone and push straight away.
An existing account is otherwise left untouched by an import. The one exception is the case where the searched identifier matched nothing locally but the directory resolved it to a login that does already exist — for instance an import by registration number for someone whose account predates that number being recorded. Only then are the account's name, email and registration number synced from the directory.
If an import reports users as not found, check that artemis.user-management.ldap.enabled is true and
that the identifiers match what the directory holds. A registration number that does not exist in the
directory cannot be resolved, since Artemis has nothing else to look the person up by.
The activated flag
activated decides whether an account may authenticate at all. It is enforced by:
- every authentication provider:
ArtemisInternalAuthenticationProvider,LdapAuthenticationProvider,SAML2Service,OIDCService, and the passkey provider - git over HTTPS (
LocalVCServletService) and git over SSH (GitPublickeyAuthenticatorService)
An account is created unactivated only when its own owner is expected to activate it, which means the
account must be internal. The activation key is redeemable only through GET /activate, so an
externally managed account — which authenticates against the external directory — is never sent an
activation mail and cannot redeem a key. Creating one unactivated therefore yields an account that
nothing can ever activate. Historically such an account could still pass web login on an LDAP-only
instance, because the LDAP provider was the one provider that did not consult the flag while git and every
other provider refused it. That gap is now closed, so an account in this state cannot authenticate at all.
GET /activate and the mail carrying the key are both gated behind
artemis.user-management.registration.enabled, so on an instance with self-registration disabled even an
internal account has no way to redeem a key. Account creation is deliberately not narrowed to match, and
checks only whether the account is internal: the LTI launch is the other path that creates an internal
account through this factory. The launch discards the key immediately, because it never sends the mail that
carries one, and records whether it still owes that account the password dialog in UserLti.initialized
rather than in activated — see LTI account provisioning below.
Consequently there are exactly three reasons for activated = false:
| Reason | Set by | Activation key |
|---|---|---|
| Awaiting activation | UserCreationService.createUser for an internal account, UserService.registerUser (always internal) | Present |
| Deliberately deactivated | UserCreationService.deactivateUser, admin edit form — any account type | Absent |
| Anonymized by an older version | UserService.anonymizeUser, together with is_deleted; no longer written, see Deleting User Accounts | Absent |
The activation key therefore distinguishes an account still waiting to be activated from one an administrator switched off on purpose. That distinction matters when repairing data: activating rows that carry a key never re-enables an account somebody deactivated deliberately.
Activation and cleanup
A self-registered account is mailed a link containing its activation key. Redeeming it through
GET /activate sets activated = true and clears the key. Every other write that activates an account
clears it too — the administrative activate action, and the LTI launch both when it creates the account and
when it hands over the generated password — so an activated account never carries one.
An account that is not activated within registration.cleanup-time-minutes (default 60) is
deleted. The removal is scheduled when the account is registered and cancelled when it is
activated, so the timer applies only to self-registration — no other provisioning path schedules it.
Deactivation versus deletion
- Deactivation keeps the account and its data but refuses authentication. It also revokes the
account's credentials — personal and participation VCS access tokens, SSH keys, and passkeys — because
the git paths accept a token or key on its own, so deactivation would otherwise not take effect there
until those credentials expired. Both routes that deactivate an account — the deactivate endpoint and
the admin edit form — write a
DEACTIVATE_USERentry to the audit log naming the administrator who did it and the account affected; activation is recorded asACTIVATE_USERthe same way. These are kept far longer than login records. Any activation or password-reset key still outstanding is discarded as well, so a link mailed before the deactivation cannot be used to get back in. - Nothing lets an account holder reverse a deactivation, through an authentication provider or otherwise. Only an administrator can activate an account again.
- Permanent deletion removes the account row together with the data that belongs to it, after an administrator has reviewed an impact preview and confirmed it, or after the retention cleanup has selected the account and every business-domain reference to it is gone. It cannot be undone. See Deleting User Accounts for the dialog, the accounts Artemis refuses to delete, and the automatic paths.
LTI account provisioning
An LTI launch for a user Artemis does not know creates an internal account and shows that user a dialog with a generated password, which is what they authenticate with afterwards. Two things follow from that:
- The account is created with an activation key like any internal account, and the launch discards it immediately. No activation mail is sent for such an account, so a key left in place would be a link nobody ever received that still activates the account.
- Whether the password dialog is still owed is recorded by the lti module itself, in
user_lti.initialized, and never inferred fromactivated.
That second point matters because activated would otherwise answer two unrelated questions with the same
value. To the launch, activated = false used to mean "this account still has to be initialised"; to an
administrator it means "this account is disabled". An account that an administrator had deactivated was
therefore indistinguishable from one that had never been initialised, and PUT api/account/users/initialize
activated it again and handed out a working password. Reaching that endpoint needs only an authenticated
session, and one issued before the deactivation keeps working.
With a separate marker, initialisation happens at most once per account: the marker is claimed in a single conditional statement, so a second — or concurrent — call finds nothing to claim, and a deactivated account was initialised earlier and so carries the marker already. It can also happen zero times, which is the point: an account deactivated before it ever completed a launch is refused rather than handed a password.
The launch itself does not check account state. With artemis.lti.trust-external-lti-systems enabled, a
deactivated account relaunching from the LMS is still issued a session, even though initialisation now
refuses it. Enforcing account state on the launch is tracked separately.
Enabling self-registration
Self-registration is disabled unless artemis.user-management.registration.enabled is explicitly
true; a missing section counts as disabled. Registered accounts are always internal.
artemis:
user-management:
registration:
enabled: true
allowed-email-pattern: '[a-zA-Z0-9_\-\.\+]+@(tum\.de|in\.tum\.de|mytum\.de)'
allowed-email-pattern-readable: '@tum.de, @in.tum.de, @mytum.de'
cleanup-time-minutes: 60 # non-activated accounts are deleted after this many minutes
spring:
mail:
host: <host>
port: 25
username: <username>
password: <password>
protocol: smtp
tls: true
properties.mail.smtp:
auth: true
starttls:
enable: true
ssl:
trust: <host>
jhipster:
mail:
base-url: https://artemis.tum.de
from: artemis@xcit.tum.de
management:
health:
mail:
enabled: true
Users can then register on the start page with any address matching allowed-email-pattern. If no
pattern is configured, any address is accepted. A working mail server is required: without it the
activation mail is never delivered and the account is deleted once the cleanup time elapses.
Related authentication settings
Each of the external mechanisms is enabled by its own property. ldap.enabled and saml2.enabled have
no default — Artemis refuses to start if they are absent — while oidc.enabled defaults to false.
artemis:
user-management:
ldap:
enabled: true # required; LDAP lookups for user details and LDAP authentication
url: <url>
user-dn: <user-dn>
password: <password>
base: <base>
allowed-username-pattern: '^([a-z]{2}\d{2}[a-z]{3})$'
saml2:
enabled: false # required
oidc:
enabled: false # optional, defaults to false
use-external: true # advertises external password reset to the client; does NOT enable or disable LDAP
use-external does not switch LDAP authentication on or off, despite its name. It is only reported
through /management/info so the client knows whether to send users to an external password reset.
LDAP is controlled exclusively by artemis.user-management.ldap.enabled.
See SAML2 Login and Registration and OIDC Login and Registration for the provider-specific setup.
Login form and rate limits
The login form asks for an identifier first and then calls GET /login-options to decide whether to
show a password field or send the user to the configured identity provider. The answer is derived from
local account state only: an internal account gets the password form, and everything else — an external
account, and an identifier this instance has not seen — is sent to the identity provider, which is also
where a first-time user is provisioned.
The account endpoints are rate limited in two independent places, and both are needed: nginx keys on the real TCP peer, while the application-level limiter runs per Artemis node and covers deployments that do not terminate at this nginx configuration.
| Endpoint | nginx zone | Application limit |
|---|---|---|
POST /authenticate, /login/webauthn | loginlimit, 30 r/m | AUTHENTICATION, 30 rpm |
POST /saml2 | loginlimit, 30 r/m | none — the SAML2 callback exchanges an already established assertion |
GET /login-options | loginoptionslimit, 30 r/m | LOGIN_OPTIONS, 30 rpm |
/register, /activate, /account/reset-password/* | accountrecoverylimit, 5 r/m | ACCOUNT_MANAGEMENT, 5 rpm |
git credential handshake (/info/refs) | gitauthlimit, 60 r/m | AUTHENTICATION, 30 rpm |
Application-level limits can be overridden per type under artemis.rate-limiting, for example
artemis.rate-limiting.login-options-requests-per-minute. Rate limiting as a whole is off unless
artemis.rate-limiting.enabled is true.
If you run your own reverse proxy instead of the shipped nginx configuration, mirror the zones above; otherwise only the application-level limits apply — and the SAML2 callback, which has none of its own, is then unbounded.
Audit log
Deliberate changes to an account are recorded in the audit log, which keeps them far longer than login records. The entries relevant to account state:
| Event type | Recorded when | Principal | Data |
|---|---|---|---|
ACTIVATE_USER | An account is activated, by an administrator or by its owner redeeming an activation key | Whoever performed it, or system if unauthenticated | user=<login> |
DEACTIVATE_USER | An account is deactivated, through either the deactivate action or the admin edit form | The acting administrator | user=<login> |
ADMIN_CHANGE_USER_PASSWORD | An administrator sets a user's password | The acting administrator | Which credential types were revoked |
All three are classified as security events, so they are written to security_audit_event and appear under
the Security tab of Server Administration → Audits in the admin UI, or through GET /api/admin/audits.
DEACTIVATE_USER in particular is the only audit record of a wholesale credential revocation, since the
revocation itself writes none. Retention is configured separately per log; see
Cleanup Service.
Troubleshooting
A user can sign in to the website but git authentication fails.
Their account is not activated. This combination is only possible on a version where the LDAP provider did
not yet consult the flag, since it now does; on such a version web login succeeded while both git paths
refused. Check activated for the account and activate it if it was never meant to be off.
A user reports they cannot sign in at all after being deactivated. That is the intended effect: deactivation refuses authentication through every provider and revokes the account's tokens, SSH keys and passkeys. Only an administrator can activate the account again, and the revoked credentials have to be created anew afterwards.
A newly registered account disappeared.
An account that is not activated within registration.cleanup-time-minutes is deleted. Usually the
activation mail was never delivered — verify the mail configuration and that
management.health.mail.enabled reports healthy.
An imported student has no registration number. Only accounts resolved through the directory carry one. An account created on first login through SAML2 or OIDC has one only if the provider supplies it through the configured registration-number attribute.
Related pages
- Access Rights — roles, groups and what each may do
- SAML2 Login and Registration
- OpenID Connect Login
- Deleting User Accounts — deactivation, permanent deletion, and automatic cleanup of accounts
- Cleanup Service — removing inactive accounts and pruning the audit log