tgstation-server 6.16.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
UserAuthority.cs
Go to the documentation of this file.
1using System;
4using System.Linq;
7
8using GreenDonut;
9
11
15
28
30{
33 {
38
43
48
53
58
63
68
73
78
83
94 IDatabaseContext databaseContext,
95 CancellationToken cancellationToken)
96 {
97 ArgumentNullException.ThrowIfNull(ids);
98 ArgumentNullException.ThrowIfNull(databaseContext);
99
100 return databaseContext
101 .Users
102 .AsQueryable()
103 .Where(x => ids.Contains(x.Id!.Value))
104 .ToDictionaryAsync(user => user.Id!.Value, cancellationToken);
105 }
106
114 [DataLoader]
115 public static async ValueTask<ILookup<long, GraphQL.Types.OAuth.OAuthConnection>> GetOAuthConnections(
117 IDatabaseContext databaseContext,
118 CancellationToken cancellationToken)
119 {
120 ArgumentNullException.ThrowIfNull(userIds);
121 ArgumentNullException.ThrowIfNull(databaseContext);
122
123 var list = await databaseContext
125 .AsQueryable()
126 .Where(x => userIds.Contains(x.User!.Id!.Value))
127 .ToListAsync(cancellationToken);
128
129 return list.ToLookup(
131 x => new GraphQL.Types.OAuth.OAuthConnection(x.ExternalUserId!, x.Provider));
132 }
133
141 [DataLoader]
142 public static async ValueTask<ILookup<long, GraphQL.Types.OAuth.OidcConnection>> GetOidcConnections(
144 IDatabaseContext databaseContext,
145 CancellationToken cancellationToken)
146 {
147 ArgumentNullException.ThrowIfNull(userIds);
148 ArgumentNullException.ThrowIfNull(databaseContext);
149
150 var list = await databaseContext
152 .AsQueryable()
153 .Where(x => userIds.Contains(x.User!.Id!.Value))
154 .ToListAsync(cancellationToken);
155
156 return list.ToLookup(
158 x => new GraphQL.Types.OAuth.OidcConnection(x.ExternalUserId!, x.SchemeKey!));
159 }
160
168 {
170 if (userInvalidWithNullName || (model.Name != null && String.IsNullOrWhiteSpace(model.Name)))
171 return BadRequest<User>(ErrorCode.UserMissingName);
172
173 model.Name = model.Name?.Trim();
174 if (model.Name != null && model.Name.Contains(':', StringComparison.InvariantCulture))
175 return BadRequest<User>(ErrorCode.UserColonInName);
176 return null;
177 }
178
196 IAuthenticationContext authenticationContext,
197 IDatabaseContext databaseContext,
209 : base(
210 authenticationContext,
211 databaseContext,
212 logger)
213 {
224 }
225
237 {
238 if (createRequest.OAuthConnections?.Any(x => x == null) == true)
239 {
240 failResponse = BadRequest<User>(ErrorCode.ModelValidationFailure);
241 return true;
242 }
243
246 var hasOAuthConnections = (createRequest.OAuthConnections?.Count > 0) == true;
249 {
250 failResponse = BadRequest<User>(ErrorCode.UserMismatchPasswordSid);
251 return true;
252 }
253
254 var hasZeroLengthPassword = createRequest.Password?.Length == 0;
256 {
258 {
259 if (createRequest.OAuthConnections == null)
260 throw new InvalidOperationException($"Expected {nameof(UserCreateRequest.OAuthConnections)} to be set here!");
261
262 if (createRequest.OAuthConnections.Count == 0)
263 {
264 failResponse = BadRequest<User>(ErrorCode.ModelValidationFailure);
265 return true;
266 }
267 }
268 else if (hasZeroLengthPassword)
269 {
270 failResponse = BadRequest<User>(ErrorCode.ModelValidationFailure);
271 return true;
272 }
273 }
274
275 if (createRequest.Group != null && createRequest.PermissionSet != null)
276 {
277 failResponse = BadRequest<User>(ErrorCode.UserGroupAndPermissionSet);
278 return true;
279 }
280
281 createRequest.Name = createRequest.Name?.Trim();
282 if (createRequest.Name?.Length == 0)
283 createRequest.Name = null;
284
285 if (!(createRequest.Name == null ^ createRequest.SystemIdentifier == null))
286 {
287 failResponse = BadRequest<User>(ErrorCode.UserMismatchNameSid);
288 return true;
289 }
290
292 return failResponse != null;
293 }
294
298
301 {
303 return Forbid<User>();
304
305 User? user;
306 if (includeJoins)
307 {
308 var queryable = Queryable(true, true);
309
310 user = await queryable.FirstOrDefaultAsync(
311 dbModel => dbModel.Id == id,
313 }
314 else
315 user = await usersDataLoader.LoadAsync(id, cancellationToken);
316
317 if (user == default)
318 return NotFound<User>();
319
321 return Forbid<User>();
322
323 return new AuthorityResponse<User>(user);
324 }
325
328 => Queryable(includeJoins, false);
329
331 public async ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken)
332 => new AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>(
334
336 public async ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OidcConnection[]>> OidcConnections(long userId, CancellationToken cancellationToken)
337 => new AuthorityResponse<GraphQL.Types.OAuth.OidcConnection[]>(
339
344 CancellationToken cancellationToken)
345 {
347
349 return failResponse;
350
352 .Users
353 .AsQueryable()
354 .CountAsync(cancellationToken);
355 if (totalUsers >= generalConfigurationOptions.Value.UserLimit)
356 return Conflict<User>(ErrorCode.UserLimitReached);
357
359 if (dbUser == null)
360 return Gone<User>();
361
362 if (createRequest.SystemIdentifier != null)
363 try
364 {
366 if (sysIdentity == null)
367 return Gone<User>();
368 dbUser.Name = sysIdentity.Username;
370 }
372 {
373 Logger.LogTrace(ex, "System identities not implemented!");
374 return new AuthorityResponse<User>(
375 new ErrorMessageResponse(ErrorCode.RequiresPosixSystemIdentity),
376 HttpFailureResponse.NotImplemented);
377 }
378 else
379 {
380 var hasZeroLengthPassword = createRequest.Password?.Length == 0;
381 var hasOAuthConnections = (createRequest.OAuthConnections?.Count > 0) == true;
382
383 // special case allow PasswordHash to be null by setting Password to "" if OAuthConnections are set
385 {
386 var result = TrySetPassword(dbUser, createRequest.Password!, true);
387 if (result != null)
388 return result;
389 }
390 }
391
393
395
397
398 Logger.LogInformation("Created new user {name} ({id})", dbUser.Name, dbUser.Id);
399
401
403 }
404
406#pragma warning disable CA1502
407#pragma warning disable CA1506 // TODO: Decomplexify
409#pragma warning restore CA1502
410#pragma warning restore CA1506
411 {
412 ArgumentNullException.ThrowIfNull(model);
413
414 if (!model.Id.HasValue || model.OAuthConnections?.Any(x => x == null) == true)
415 return BadRequest<User>(ErrorCode.ModelValidationFailure);
416
417 if (model.Group != null && model.PermissionSet != null)
418 return BadRequest<User>(ErrorCode.UserGroupAndPermissionSet);
419
423 var oAuthEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnServiceConnections);
424
428 .Users
429 .AsQueryable()
430 .Where(x => x.Id == model.Id)
431 .Include(x => x.CreatedBy)
432 .Include(x => x.OAuthConnections)
433 .Include(x => x.OidcConnections)
434 .Include(x => x.Group!)
435 .ThenInclude(x => x.PermissionSet)
436 .Include(x => x.PermissionSet)
437 .FirstOrDefaultAsync(cancellationToken);
438
439 if (originalUser == default)
440 return NotFound<User>();
441
443 return Forbid<User>();
444
445 // Ensure they are only trying to edit things they have perms for (system identity change will trigger a bad request)
446 if ((!canEditAllUsers
447 && (model.Id != originalUser.Id
448 || model.Enabled.HasValue
449 || model.Group != null
450 || model.PermissionSet != null
451 || model.Name != null))
452 || (!passwordEdit && model.Password != null)
453 || (!oAuthEdit && model.OAuthConnections != null))
454 return Forbid<User>();
455
457 var invalidateSessions = false;
458 if (originalUserHasSid && originalUser.PasswordHash != null)
459 {
460 // cleanup from https://github.com/tgstation/tgstation-server/issues/1528
461 Logger.LogDebug("System user ID {userId}'s PasswordHash is polluted, updating database.", originalUser.Id);
463
464 invalidateSessions = true;
465 }
466
467 if (model.SystemIdentifier != null && model.SystemIdentifier != originalUser.SystemIdentifier)
468 return BadRequest<User>(ErrorCode.UserSidChange);
469
470 if (model.Password != null)
471 {
473 return BadRequest<User>(ErrorCode.UserMismatchPasswordSid);
474
475 var result = TrySetPassword(originalUser, model.Password, false);
476 if (result != null)
477 return result;
478
479 invalidateSessions = true;
480 }
481
482 if (model.Name != null && User.CanonicalizeName(model.Name) != originalUser.CanonicalName)
483 return BadRequest<User>(ErrorCode.UserNameChange);
484
485 if (model.OAuthConnections != null
486 && (model.OAuthConnections.Count != originalUser.OAuthConnections!.Count
487 || !model.OAuthConnections.All(x => originalUser.OAuthConnections.Any(y => y.Provider == x.Provider && y.ExternalUserId == x.ExternalUserId))))
488 {
489 if (securityConfigurationOptions.Value.OidcStrictMode)
490 return BadRequest<User>(ErrorCode.BadUserEditDueToOidcStrictMode);
491
493 return BadRequest<User>(ErrorCode.AdminUserCannotHaveServiceConnection);
494
495 if (model.OAuthConnections.Count == 0 && originalUser.PasswordHash == null && originalUser.SystemIdentifier == null)
496 return BadRequest<User>(ErrorCode.CannotRemoveLastAuthenticationOption);
497
498 DatabaseContext.OAuthConnections.RemoveRange(originalUser.OAuthConnections);
499 originalUser.OAuthConnections.Clear();
500
501 foreach (var updatedConnection in model.OAuthConnections)
502 originalUser.OAuthConnections.Add(new Models.OAuthConnection
503 {
504 Provider = updatedConnection.Provider,
505 ExternalUserId = updatedConnection.ExternalUserId,
506 });
507 }
508
509 if (model.OidcConnections != null
510 && (model.OidcConnections.Count != originalUser.OidcConnections!.Count
511 || !model.OidcConnections.All(x => originalUser.OidcConnections.Any(y => y.SchemeKey == x.SchemeKey && y.ExternalUserId == x.ExternalUserId))))
512 {
513 if (securityConfigurationOptions.Value.OidcStrictMode)
514 return BadRequest<User>(ErrorCode.BadUserEditDueToOidcStrictMode);
515
517 return BadRequest<User>(ErrorCode.AdminUserCannotHaveServiceConnection);
518
519 if (model.OidcConnections.Count == 0 && originalUser.PasswordHash == null && originalUser.SystemIdentifier == null)
520 return BadRequest<User>(ErrorCode.CannotRemoveLastAuthenticationOption);
521
522 DatabaseContext.OidcConnections.RemoveRange(originalUser.OidcConnections);
523 originalUser.OidcConnections.Clear();
524 foreach (var updatedConnection in model.OidcConnections)
525 originalUser.OidcConnections.Add(new Models.OidcConnection
526 {
527 SchemeKey = updatedConnection.SchemeKey,
528 ExternalUserId = updatedConnection.ExternalUserId,
529 });
530 }
531
532 if (model.Group != null)
533 {
534 if (securityConfigurationOptions.Value.OidcStrictMode)
535 return BadRequest<User>(ErrorCode.BadUserEditDueToOidcStrictMode);
536
538 .Groups
539 .AsQueryable()
540 .Where(x => x.Id == model.Group.Id)
541 .Include(x => x.PermissionSet)
542 .FirstOrDefaultAsync(cancellationToken);
543
544 if (originalUser.Group == default)
545 return Gone<User>();
546
547 DatabaseContext.Groups.Attach(originalUser.Group);
548 if (originalUser.PermissionSet != null)
549 {
550 Logger.LogInformation("Deleting permission set {permissionSetId}...", originalUser.PermissionSet.Id);
551 DatabaseContext.PermissionSets.Remove(originalUser.PermissionSet);
553 }
554 }
555 else if (model.PermissionSet != null)
556 {
557 if (securityConfigurationOptions.Value.OidcStrictMode)
558 return BadRequest<User>(ErrorCode.BadUserEditDueToOidcStrictMode);
559
560 if (originalUser.PermissionSet == null)
561 {
562 Logger.LogTrace("Creating new permission set...");
563 originalUser.PermissionSet = new Models.PermissionSet();
564 }
565
568
569 originalUser.Group = null;
571 }
572
573 var fail = CheckValidName(model, false);
574 if (fail != null)
575 return fail;
576
578
579 if (model.Enabled.HasValue)
580 {
581 if (securityConfigurationOptions.Value.OidcStrictMode)
582 return BadRequest<User>(ErrorCode.BadUserEditDueToOidcStrictMode);
583
584 invalidateSessions = originalUser.Require(x => x.Enabled) && !model.Enabled.Value;
585 originalUser.Enabled = model.Enabled.Value;
586 }
587
590
592
593 Logger.LogInformation("Updated user {userName} ({userId})", originalUser.Name, originalUser.Id);
594
597
599
600 // return id only if not a self update and cannot read users
603 return canReadBack
606 }
607
615 GraphQL.Subscriptions.UserSubscriptions.UserUpdatedTopics(
616 user.Require(x => x.Id))
617 .Select(topic => topicEventSender.SendAsync(
618 topic,
620 CancellationToken.None))); // DCT: Operation should always run
621
629 {
632 .Users
633 .AsQueryable();
634
635 if (!allowSystemUser)
637 .Where(user => user.CanonicalName != tgsUserCanonicalName);
638
639 if (includeJoins)
641 .Include(x => x.CreatedBy)
642 .Include(x => x.OAuthConnections)
643 .Include(x => x.OidcConnections)
644 .Include(x => x.Group!)
645 .ThenInclude(x => x.PermissionSet)
646 .Include(x => x.PermissionSet);
647
648 return queryable;
649 }
650
657 async ValueTask<User> CreateNewUserFromModel(Api.Models.Internal.UserApiBase model, CancellationToken cancellationToken)
658 {
659 Models.PermissionSet? permissionSet = null;
660 UserGroup? group = null;
661 if (model.Group != null)
663 .Groups
664 .AsQueryable()
665 .Where(x => x.Id == model.Group.Id)
666 .Include(x => x.PermissionSet)
667 .FirstOrDefaultAsync(cancellationToken);
668 else
669 permissionSet = new Models.PermissionSet
670 {
672 InstanceManagerRights = model.PermissionSet?.InstanceManagerRights ?? InstanceManagerRights.None,
673 };
674
675 return new User
676 {
677 CreatedAt = DateTimeOffset.UtcNow,
678 CreatedBy = AuthenticationContext.User,
679 Enabled = model.Enabled ?? false,
680 PermissionSet = permissionSet,
681 Group = group,
682 Name = model.Name,
683 SystemIdentifier = model.SystemIdentifier,
685 .OAuthConnections
686 ?.Select(x => new Models.OAuthConnection
687 {
688 Provider = x.Provider,
689 ExternalUserId = x.ExternalUserId,
690 })
691 .ToList()
692 ?? new List<Models.OAuthConnection>(),
695 ?.Select(x => new Models.OidcConnection
696 {
697 SchemeKey = x.SchemeKey,
698 ExternalUserId = x.ExternalUserId,
699 })
700 .ToList()
701 ?? new List<Models.OidcConnection>(),
702 };
703 }
704
713 {
714 newPassword ??= String.Empty;
715 if (newPassword.Length < generalConfigurationOptions.Value.MinimumPasswordLength)
716 return new AuthorityResponse<User>(
717 new ErrorMessageResponse(ErrorCode.UserPasswordLength)
718 {
719 AdditionalData = $"Required password length: {generalConfigurationOptions.Value.MinimumPasswordLength}",
720 },
721 HttpFailureResponse.BadRequest);
722 cryptographySuite.SetUserPassword(dbUser, newPassword, newUser);
723 return null;
724 }
725 }
726}
Represents initial credentials used by the server.
static readonly string AdminUserName
The name of the default admin user.
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:14
Represents a set of server permissions.
AdministrationRights? AdministrationRights
The Rights.AdministrationRights for the user.
Represents an error message returned by the server.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
ILogger< AuthorityBase > Logger
Gets the ILogger for the AuthorityBase.
readonly ISessionInvalidationTracker sessionInvalidationTracker
The ISessionInvalidationTracker for the UserAuthority.
readonly ITopicEventSender topicEventSender
The ITopicEventSender for the UserAuthority.
AuthorityResponse< User >? TrySetPassword(User dbUser, string newPassword, bool newUser)
Attempt to change the password of a given dbUser .
readonly IPermissionsUpdateNotifyee permissionsUpdateNotifyee
The IPermissionsUpdateNotifyee for the UserAuthority.
async ValueTask< AuthorityResponse< User > > GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken)
Gets the User with a given id .A ValueTask<TResult> resulting in a User AuthorityResponse<TResult>.
static Task< Dictionary< long, User > > GetUsers(IReadOnlyList< long > ids, IDatabaseContext databaseContext, CancellationToken cancellationToken)
Implements the usersDataLoader.
async ValueTask< AuthorityResponse< GraphQL.Types.OAuth.OidcConnection[]> > OidcConnections(long userId, CancellationToken cancellationToken)
Gets the GraphQL.Types.OAuth.OidcConnections for the User with a given userId .A ValueTask<TResult> r...
async ValueTask< AuthorityResponse< GraphQL.Types.OAuth.OAuthConnection[]> > OAuthConnections(long userId, CancellationToken cancellationToken)
Gets the GraphQL.Types.OAuth.OAuthConnections for the User with a given userId .A ValueTask<TResult> ...
UserAuthority(IAuthenticationContext authenticationContext, IDatabaseContext databaseContext, ILogger< UserAuthority > logger, IUsersDataLoader usersDataLoader, IOAuthConnectionsDataLoader oAuthConnectionsDataLoader, IOidcConnectionsDataLoader oidcConnectionsDataLoader, ISystemIdentityFactory systemIdentityFactory, IPermissionsUpdateNotifyee permissionsUpdateNotifyee, ICryptographySuite cryptographySuite, ISessionInvalidationTracker sessionInvalidationTracker, ITopicEventSender topicEventSender, IOptionsSnapshot< GeneralConfiguration > generalConfigurationOptions, IOptions< SecurityConfiguration > securityConfigurationOptions)
Initializes a new instance of the UserAuthority class.
static ? AuthorityResponse< User > CheckValidName(UserUpdateRequest model, bool newUser)
Check if a given model has a valid UserName.Name specified.
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the UserAuthority.
readonly IOptions< SecurityConfiguration > securityConfigurationOptions
The IOptions<TOptions> of SecurityConfiguration for the UserAuthority.
readonly IOptionsSnapshot< GeneralConfiguration > generalConfigurationOptions
The IOptionsSnapshot<TOptions> of GeneralConfiguration for the UserAuthority.
IQueryable< User > Queryable(bool includeJoins)
Gets all registered Users.A IQueryable<T> of Users.
IQueryable< User > Queryable(bool includeJoins, bool allowSystemUser)
Gets all registered Users.
async ValueTask< User > CreateNewUserFromModel(Api.Models.Internal.UserApiBase model, CancellationToken cancellationToken)
Creates a new User from a given model .
ValueTask< AuthorityResponse< User > > Read(CancellationToken cancellationToken)
Gets the currently authenticated user.A ValueTask<TResult> resulting in a User AuthorityResponse<TRes...
static bool BadCreateRequestChecks(UserCreateRequest createRequest, bool? needZeroLengthPasswordWithOAuthConnections, [NotNullWhen(true)] out AuthorityResponse< User >? failResponse)
Checks if a createRequest should return a bad request AuthorityResponse<TResult>.
static async ValueTask< ILookup< long, GraphQL.Types.OAuth.OAuthConnection > > GetOAuthConnections(IReadOnlyList< long > userIds, IDatabaseContext databaseContext, CancellationToken cancellationToken)
Implements the oAuthConnectionsDataLoader.
static async ValueTask< ILookup< long, GraphQL.Types.OAuth.OidcConnection > > GetOidcConnections(IReadOnlyList< long > userIds, IDatabaseContext databaseContext, CancellationToken cancellationToken)
Implements the oidcConnectionsDataLoader.
readonly IOidcConnectionsDataLoader oidcConnectionsDataLoader
The IOidcConnectionsDataLoader for the UserAuthority.
async ValueTask< AuthorityResponse< User > > Create(UserCreateRequest createRequest, bool? needZeroLengthPasswordWithOAuthConnections, CancellationToken cancellationToken)
Creates a User.A ValueTask<TResult> resulting in am AuthorityResponse<TResult> for the created User.
readonly ICryptographySuite cryptographySuite
The ICryptographySuite for the UserAuthority.
ValueTask SendUserUpdatedTopics(User user)
Send topics through the topicEventSender indicating a given user was created or updated.
readonly IUsersDataLoader usersDataLoader
The IUsersDataLoader for the UserAuthority.
async ValueTask< AuthorityResponse< User > > Update(UserUpdateRequest model, CancellationToken cancellationToken)
Updates a User.A ValueTask<TResult> resulting in am AuthorityResponse<TResult> for the created User.
readonly IOAuthConnectionsDataLoader oAuthConnectionsDataLoader
The IOAuthConnectionsDataLoader for the UserAuthority.
Backend abstract implementation of IDatabaseContext.
DbSet< OAuthConnection > OAuthConnections
The OAuthConnections in the DatabaseContext.
DbSet< PermissionSet > PermissionSets
The PermissionSets in the DatabaseContext.
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext.A Task representing the running operation.
DbSet< User > Users
The Users in the DatabaseContext.
DbSet< OidcConnection > OidcConnections
The OidcConnections in the DatabaseContext.
DbSet< UserGroup > Groups
The UserGroups in the DatabaseContext.
Represents a group of Users.
Definition UserGroup.cs:16
const string TgsSystemUserName
Username used when creating jobs automatically.
Definition User.cs:21
static string CanonicalizeName(string name)
Change a UserName.Name into a CanonicalName.
string? CanonicalName
The uppercase invariant of UserName.Name.
Definition User.cs:58
ulong GetRight(RightsType rightsType)
Get the value of a given rightsType .The value of rightsType . Note that if InstancePermissionSet is ...
IDatabaseCollection< OidcConnection > OidcConnections
The DbSet<TEntity> for OidcConnections.
IDatabaseCollection< User > Users
The Users in the IDatabaseContext.
IDatabaseCollection< OAuthConnection > OAuthConnections
The DbSet<TEntity> for OAuthConnections.
Represents a host-side model that may be transformed into a TApiModel .
For creating and accessing authentication contexts.
Contains various cryptographic functions.
Receives notifications about permissions updates.
ValueTask UserDisabled(User user, CancellationToken cancellationToken)
Called when a given User is successfully disabled.
void UserModifiedInvalidateSessions(User user)
Invalidate all sessions for a given user .
Task< ISystemIdentity?> CreateSystemIdentity(User user, CancellationToken cancellationToken)
Create a ISystemIdentity for a given user .
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12
@ List
User may list files if the Models.Instance allows it.
RightsType
The type of rights a model uses.
Definition RightsType.cs:7
InstanceManagerRights
Rights for managing Models.Instances.
AdministrationRights
Administration rights for the server.
@ Api
The ApiHeaders.ApiVersionHeader header is missing or invalid.
HttpFailureResponse
Indicates the type of HTTP status code an failing AuthorityResponse should generate.
HttpSuccessResponse
Indicates the type of HTTP status code a successful AuthorityResponse<TResult> should generate.
@ Enabled
The OAuth Gateway is enabled.