Document new authentication pipeline

Also fix location of `TgsAuthorizeAttribute` .md documentation.
This commit is contained in:
Jordan Dominion
2023-11-05 00:01:02 -04:00
parent ec81fcdf41
commit dd2e264477
2 changed files with 68 additions and 3 deletions
@@ -12,4 +12,3 @@ Some notable exceptions:
- Returns 401 If an `IAuthenticationContext` could not be created for a request.
- [BridgeController](./BridgeController.cs) is a special controller accessible only from localhost and is used to receive bridge request from DreamDaemon
- [HomeController](./HomeController.cs) contains the code to initially log in and generate an API token for a user.
- [TgsAuthorizeAttribute](./TgsAuthorizeAttribute.cs) is a special attribute applied to controller methods to define which rights are required to run a verb.
+68 -2
View File
@@ -1,12 +1,78 @@
# Security Classes
- [IAuthenticationContext](./IAuthenticationContext.cs) and [implementation](./AuthenticationContext.cs) is what contains information about an authenticated user for a request. Includes things like the relevant `InstanceUser` and any associated rights.
- [IAuthenticationContextFactory](./IAuthenticationContextFactory.cs) and [implementation](AuthenticationContextFactory.cs) is a factory for `IAuthenticationContext`s. It handles things related to the database for a user's authentication. This includes loading their rights/associated instance user. It will also stop the request if the users token was issued before the last time their password or enabled status was updated.
- [IClaimsInjector](./IClaimsInjector.cs) and [implementation](./ClaimsInjector.cs) is used to associate rights with a request context so that it may properly pass appropriate `TgsAuthorizeAttribute`s.
- [IAuthenticationContextFactory](./IAuthenticationContextFactory.cs) and [implementation](AuthenticationContextFactory.cs) is a factory for `IAuthenticationContext`s. It handles things related to the database for a user's authentication. This includes loading their rights/associated instance user. It will also stop the request if the users token was issued before the last time their password or `Enabled` status was updated.
- [AuthenticationContextClaimsTransformation](./AuthenticationContextClaimsTransformation.cs) is used to associate rights with a request context so that it may properly pass appropriate `TgsAuthorizeAttribute`s.
- [ICrytopgraphySuite](./ICrytopgraphySuite.cs) and [implementation](./CrytopgraphySuite.cs) is used to generate secure strings and byte arrays. It also contains the password hashing and validation logic.
- [IIdentityCache](./IIdentityCache.cs) and [implementation](./IdentityCache.cs) is used to store `ISystemIdentity`s for the duration of their associated tokens as [IdentityCacheObject](./IdentityCacheObject.cs)s.
- [ITokenFactory](./ITokenFactory.cs) and [implementation](./TokenFactory.cs) is used to generate the Json Web Token for a session after a user successfully authenticates.
- [ISystemIdentity](./ISystemIdentity.cs)s represent a logon session with the operating system for a given user. It contains a method to run code under the security context of said user.
- [ISystemIdentityFactory](./ISystemIdentityFactory.cs) is used to create `ISystemIdentity`s by attempting to log the user in with the OS with a given username and password.
- [TgsAuthorizeAttribute](./TgsAuthorizeAttribute.cs) is a special attribute applied to controller methods to define which rights are required to run a verb.
- [OAuth](./OAuth) contains classes related to OAuth 2.0 authentication
# A Basic Rundown of the Authenticaton Pipeline
## For the login request (`POST /`)
1. An attempt to parse the `ApiHeaders` is made. If they were valid. The API version check is performed. If it fails, HTTP 426 with an `ErrorMessageResponse` will be returned.
1. If, for some reason, the user attempts to use a JWT to authenticate this request, steps 2-4 of the non-login pipeline list below are performed.
1. The `ApiController` base class inspects the request.
- At this point, if the `ApiHeaders` (MINUS the `Authorization` header) cannot be properly parsed, HTTP 400 with an `ErrorMessageResponse` is returned.
1. The `HomeController` inspects the request.
1. If the `ApiHeaders` could not be properly parsed, HTTP 400 (or 406 if the `Accept` header was bad) with an `ErrorMessageResponse` is returned.
- The `WWW-Authenticate` header will be set in this response.
1. If authentication succeeded using a JWT `Bearer` token, HTTP 400 with an `ErrorMessageResponse` is returned. Refreshing a login using a token is not permitted.
- If the user is using a username/password combo
1. The username and password combination is tried against the OS authentication system (currently a no-op on Linux).
- If it succeeds, the session is held on to for future reference and the database is queried for a user matching the SID/UID of the login session.
- Otherwise, the database is queried for a user matching the canonicalized username.
- If the user is using an OAuth code:
1. If the OAuth provider is disabled in the configuration, HTTP 400 with an `ErrorMessageResponse` is returned.
1. The code is sent to the external provider for validation
- If the provider is GitHub, there's a chance that this could fail due to rate limiting. In this case, HTTP 429 is returned.
- If the provider rejects the OAuth code, HTTP 401 is returned.
1. The database is queried for a user matching the OAuth provider and external user identifier sent with the OAuth provider's response.
1. If the query selected above produces no results, HTTP 401 is returned.
1. For non-OAuth logins, maintenance is performed on the user's DB entry at this point
- For non-OS logins:
1. The provided password is hashed and checked against the database entry. If it does not match, HTTP 401 will be returned.
- This can potentially cause a change to the DB's stored `PasswordHash` if TGS has updated its dependencies and Microsoft has decided to deprecated the previous hashing method since the user last logged in.
- If this occurs, it invalidates all previous logins for the user.
- For OS logins:
1. If the `PasswordHash` in the DB isn't null, it is set as such. This invalidates all previous logins for the user.
1. If the `Name` in the DB does not match the user's OS login, it is updated.
1. If the user's database entry says they are not enabled, HTTP 403 is returned.
1. A token is generated from the [ITokenFactory](./ITokenFactory.cs).
1. For OS logins, the user's login session is cached for the duration of the token's validity plus 15 seconds.
1. The token is returned as a `TokenResponse` with an HTTP 200 status code.
## For all other authenticated requests
1. An attempt to parse the `ApiHeaders` is made. If they were valid. The API version check is performed. If it fails, HTTP 426 with an `ErrorMessageResponse` will be returned.
1. The JWT, if present, is validated. If it is, the scope's [AuthenticationContextFactory](./AuthenticationContextFactory.cs) has `SetTokenNbf` called. If not, HTTP 401 will be returned.
- Inside ASP.NET Core, this initializes the calling user's identity principal and sets the "sub" claim to the TGS user ID parsed out of the JWT.
- We know it's the user ID because we set it up like that in the [TokenFactory](./TokenFactory.cs)
1. The [AuthenticationContextClaimsTransformation](./AuthenticationContextClaimsTransformation.cs) is run (this does not short circuit to responses).
1. This invokes `IAuthenticationContextFactory.CreateAuthenticationContext` using the "sub" claim from the user's identity and the "nbf" timestamp set earlier (We don't get this from the scope's [IApiHeadersProvider](./IApiHeadersProvider.cs) because there may be other errors preventing the `ApiHeaders` from being parsed).
- At this point, the database lookup using the user ID occurs. This hyrates the scope's [AuthenticationContext](./AuthenticationContext.cs) (which is available at the start of the request, but uninitialized). If the user is a system user, their login session is pulled from the cache. This is also where the instance data for a request is loaded if the user has a valid `InstancePermissionSet` for that instance. The user needs to have a few prerequisites for a valid [IAuthenticationContext](./IAuthenticationContext.cs) to be generated:
- The user with the matching ID must exist in the database.
- The last time the user's password or `Enabled` status changed must be before the "nbf" of their token"
- If the user logged in with an OS login, the session is retrieved from the cache here and added to the scope's [AuthenticationContext](./AuthenticationContext.cs).
1. If a valid authentication context is returned from the [IAuthenticationContextFactory](./IAuthenticationContextFactory.cs), the [AuthenticationContextClaimsTransformation](./AuthenticationContextClaimsTransformation.cs) uses the context to add claims for each permission bit to the user's identity principal.
- Internally, ASP.NET Core uses this to determine whether or not a request to an endpoint will 403 or not based on the parameters of its [TgsAuthorizeAttribute](./TgsAuthorizeAttribute.cs).
1. The authorization filter is invoked
- For non-SignalR hub requests, this is the [AuthenticationContextAuthorizationFilter](./AuthenticationContextAuthorizationFilter.cs). It does two simple things:
1. It checks the validity of the scope's [IAuthenticationContext](./IAuthenticationContext.cs). If it is invalid (indicating the user is not authorized either due to not existing (Only possible with a forged and signed JWT) or if their token was outdated compared to the last time their password or `Enabled` status was updated), HTTP 401 will be returned.
1. It checks the user's `Enabled` status. If the user is disabled, HTTP 403 will be returned.
- For SignalR hub requests, this is the [AuthorizationContextHubFilter](./AuthorizationContextHubFilter.cs).
- If either [IAuthenticationContext](./IAuthenticationContext.cs) is either invalid OR unauthorized, it invokes `IErrorHandlingHub.AbortingConnection` with `ConnectionAbortReason.TokenInvalid` on the client before aborting the connection.
1. The `ApiController` base class inspects the request.
1. If the `ApiHeaders` could not be properly parsed, HTTP 400 (or 406 if the `Accept` header was bad) with an `ErrorMessageResponse` is returned.
1. If the request is to an Instance component path:
1. If there is no valid `Instance` header, HTTP 400 with an `ErrorMessageResponse` is returned.
1. If the active [IAuthenticationContext](./IAuthenticationContext.cs) has no instance data loaded (indicating the user is not authorized to access said instance), HTTP 403 is returned.
1. If the instance is offline, HTTP 409 with an `ErrorMessageResponse` is returned.
1. If the request takes an API model as a parameters and the model included in the request body encountered validation errors, HTTP 400 with an `ErrorMessageResponse` is returned.
1. The request at this point, is considered authorized. Remaining behaviour is left up to each individual route to implement.