User Access Control

General UAC utilities

General user access control methods

sni.uac.uac.is_authorized_to_login(usr: sni.user.models.User) → bool[source]

Tells wether a user is authorized to login or not. A user is authorized to login if the following conditions are satisfied:

  • its authorized_to_login field is True or if the user belongs to at least one group, corporation, alliance, or coalition whose authorized_to_login field is True;

  • the user’s authorized_to_login field is not False, and none of the groups, corporation, alliance, or coalitions the user belongs to has a authorized_to_login field set ot False.

In addition, the root user is always allowed to login, regardless of the status of the group he’s part of.

Clearance management

Clearance management and verification. Each user has a clearance level, which is an integer:

  • Level -1: User is a guest and has no priviledge whatsoever.

  • Level 0: User only have access to public data and read-write access to its own ESI.

  • Level 1: User has access to read-only ESI (i.e. GET calls) of all members of his corporation.

  • Level 2: User has access to read-write ESI (i.e. all http methods) of all members of his corporation.

  • Level 3: User has access to read-only ESI (i.e. GET calls) of all members of his alliance.

  • Level 4: User has access to read-write ESI (i.e. all http methods) of all members of his alliance.

  • Level 5: User has access to read-only ESI (i.e. GET calls) of all members of his coalition.

  • Level 6: User has access to read-write ESI (i.e. all http methods) of all members of his coalition.

  • Level 7: User has access to read-only ESI (i.e. GET calls) of all members regisered on this SNI instance.

  • Level 8: User has access to read-write ESI (i.e. all http methods) of all members regisered on this SNI instance.

  • Level 9: User has clearance level 8 and some administrative priviledges.

  • Level 10: Superuser.

Furthermore, note that

  • Any user can raise any other to its own clearance level, provided they are in the same corporation (for levels 1 and 2), alliance (for levels 3 and 4), or coalition (for levels 5 and 6).

  • Demoting users is considered an administrative task and requires a clearance of at least 9.

  • Clearance levels are public informations.

See sni.uac.clearance.has_clearance() for a precise specification of how clearance levels are checked, and sni.uac.clearance.SCOPE_LEVELS for the declaration of all scopes.

class sni.uac.clearance.AbsoluteScope(level: int)[source]

Bases: sni.uac.clearance.AbstractScope

An absolute scope asserts that the source user has at least a pretermined clearance level.

has_clearance(source: sni.user.models.User, target: Optional[sni.user.models.User]) → bool[source]
level: int = None
class sni.uac.clearance.AbstractScope[source]

Bases: object

Represents an abstract scope. In SNI, a scope is a class that determines wether a source user is authorized to perform an action against a target user (see sni.uac.clearance.AbstractScope.has_clearance()).

has_clearance(source: sni.user.models.User, target: Optional[sni.user.models.User]) → bool[source]

Pure virtual methode, raises a NotImplementedError.

class sni.uac.clearance.ClearanceModificationScope(level: int)[source]

Bases: sni.uac.clearance.AbstractScope

This scope determines when a user is authorized to change the clearance level of another user. See sni.uac.clearance.ClearanceModificationScope.has_clearance().

has_clearance(source: sni.user.models.User, target: Optional[sni.user.models.User]) → bool[source]

For the source user to change the clearance level of the target user at the level indicated in sni.uac.clearance.ClearanceModificationScope.level, the clearance level of the source must be hierarchically superior to the target, and have read-write access to the target’s corporation, alliance, or coalition, whichever is most global. In addition, the source must have at least the same clearance level than the target.

level: int = None
class sni.uac.clearance.ESIScope(level: int)[source]

Bases: sni.uac.clearance.AbstractScope

This scope determines when a user is authorized to read or write ESI data.

has_clearance(source: sni.user.models.User, target: Optional[sni.user.models.User]) → bool[source]
level: int = None
sni.uac.clearance.are_in_same_alliance(user1: sni.user.models.User, user2: sni.user.models.User) → bool[source]

Tells wether two users are in the same alliance. Users that have no alliance are considered in a different alliance as everyone else.

sni.uac.clearance.are_in_same_coalition(user1: sni.user.models.User, user2: sni.user.models.User) → bool[source]

Tells wether two users have a coalition in common.

sni.uac.clearance.are_in_same_corporation(user1: sni.user.models.User, user2: sni.user.models.User) → bool[source]

Tells wether two users are in the same corporation. Users that have no corporation are considered in different a corporation as everyone else.

sni.uac.clearance.assert_has_clearance(source: sni.user.models.User, scope: str, target: Optional[sni.user.models.User] = None) → None[source]

Like sni.uac.clearance.has_clearance() but raises a PermissionError if the result is False.

sni.uac.clearance.distance_penalty(source: sni.user.models.User, target: sni.user.models.User) → int[source]

Returns 0 if both users are the same user; returns 1 if they are not the same but in the same corporation; 3 if they are not in the same corporation but in the same alliance; 5 if they are not in the same alliance but in the same coalition; and otherwise, returns 7.

sni.uac.clearance.has_clearance(source: sni.user.models.User, scope_name: str, target: Optional[sni.user.models.User] = None) → bool[source]

Check wether the source user has sufficient clearance to perform a given action (or scope) against the target user.

sni.uac.clearance.reset_clearance(usr: sni.user.models.User, save: bool = False)[source]

Resets a user’s clearance.

If a user is the CEO of its corporation, then a clearance level of 2 is granted. If its corporation is the executor of the alliance, then a level of 4 is granted instead. If the user is root or has a clearance level of 10, then a level of 10 is applied (so that superusers are preserved no matter what). Otherwise, the user’s clearance level is set to 0.

SNI token management

Token management

sni.uac.token.create_dynamic_app_token(owner: sni.user.models.User, *, callback: Optional[str] = None, comments: Optional[str] = None, parent: Optional[sni.uac.models.Token] = None) → sni.uac.models.Token[source]

Creates a new dynamic app token for that user.

Warning: Does not check that the user is allowed to actually do that.

sni.uac.token.create_permanent_app_token(owner: sni.user.models.User, *, callback: Optional[str] = None, comments: Optional[str] = None, parent: Optional[sni.uac.models.Token] = None) → sni.uac.models.Token[source]

Creates a new permanent app token for that user.

Warning: Does not check that the user is allowed to actually do that.

sni.uac.token.create_state_code(app_token: Optional[sni.uac.models.Token], *, inviting_corporation: Optional[sni.user.models.Corporation] = None, code_prefix: Optional[str] = None) → sni.uac.models.StateCode[source]

Creates a new state code.

See also

sni.uac.token.StateCode

sni.uac.token.create_user_token(app_token: sni.uac.models.Token, owner: sni.user.models.User) → sni.uac.models.Token[source]

Derives a new user token from an existing app token, and set the owner to be the user given in argument.

sni.uac.token.from_authotization_header(authorization: str = Header(None)) → sni.uac.models.Token[source]

Validates an Authorization: Bearer header and returns a sni.uac.token.Token. If the token string is invalid, raises a 401. Should be used as a FastAPI dependency.

sni.uac.token.from_authotization_header_nondyn(tkn: sni.uac.models.Token = Depends(from_authotization_header)) → sni.uac.models.Token[source]

Validates an Authorization: Bearer header and returns a sni.uac.token.Token. If the token string is invalid, or if the token is dynamic, raises a 401. Should be used as a FastAPI dependency.

sni.uac.token.get_token_from_jwt(token_str: str) → sni.uac.models.Token[source]

Retrieves a token from its JWT string.

sni.uac.token.to_jwt(model: sni.uac.models.Token) → str[source]

Derives a JWT token byte array from the a token model.

Database models

Models

class sni.uac.models.StateCode(*args, **values)[source]

Bases: mongoengine.document.Document

Represents a state code and related metadatas.

A state code is issued when a new user token is issued from a dynamic app token, and is a way for SNI to remeber about the authentication while the end user logs in to EVE SSO.

SCHEMA_VERSION = 3

Latest schema version for this collection

_version

Schema version of this document

app_token

The app token that created this state code

created_on

Timestamp of the creation of this document

inviting_corporation

Corporation inviting the user of that state code, if any

uuid

The state code. It’s not really an UUID4 but i’m too lazy to change the field name and write migration code…

class sni.uac.models.Token(*args, **values)[source]

Bases: mongoengine.document.Document

Represents a token issued by SNI.

class TokenType[source]

Bases: str, enum.Enum

Enumeration containing the various token types.

dyn = 'dyn'
per = 'per'
use = 'use'
callback

Callback URL of the application. When a new user token is issued, the corresponding application is notified at this URL.

comments

Comments

created_on

Timestamp of the creation of this document

expires_on

Self explanatory

owner

Reference to the owner of this token

parent

Optional reference to the token that has been used to create this one

token_type

Token type, see sni.uac.models.Token.TokenType

uuid

UUID of this token

Todo

Use the document’s _id field instead.

Database signals

Database signals. See Mongoengine signals

Database migration

UAC specific migrations

sni.uac.migration.ensure_root_dyn_token() → None[source]

Create a dynamic app token owned by root, if none exist.

sni.uac.migration.ensure_root_per_token() → None[source]

Create a permanent app token owned by root, if none exist.

sni.uac.migration.migrate()[source]

Migrates all schema and ensures basic documents

sni.uac.migration.migrate_state_code()[source]

Migrates the state_code collection