Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenID Connect Support and Strict Mode #2144

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions build/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="WebpanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>6.14.1</TgsCoreVersion>
<TgsConfigVersion>5.5.0</TgsConfigVersion>
<TgsRestVersion>10.12.1</TgsRestVersion>
<TgsGraphQLVersion>0.5.0</TgsGraphQLVersion>
<TgsCoreVersion>6.15.0</TgsCoreVersion>
<TgsConfigVersion>5.6.0</TgsConfigVersion>
<TgsRestVersion>10.13.0</TgsRestVersion>
<TgsGraphQLVersion>0.6.0</TgsGraphQLVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>17.0.1</TgsApiLibraryVersion>
<TgsClientVersion>20.0.0</TgsClientVersion>
<TgsApiLibraryVersion>18.0.0</TgsApiLibraryVersion>
<TgsClientVersion>21.0.0</TgsClientVersion>
<TgsDmapiVersion>7.3.1</TgsDmapiVersion>
<TgsInteropVersion>5.10.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.6.0</TgsHostWatchdogVersion>
Expand Down
1 change: 0 additions & 1 deletion src/Tgstation.Server.Api/ApiHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ void AddError(HeaderErrorTypes headerType, string message)
Password = String.Concat(basicAuthSplits.Skip(1));
break;
default:
AddError(HeaderErrorTypes.AuthorizationInvalid, "Invalid authentication scheme!");
break;
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/Tgstation.Server.Api/Models/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ public enum ErrorCode : uint
/// Attempted to set <see cref="Internal.UserApiBase.OAuthConnections"/> for the admin user.
/// </summary>
[Description("The admin user cannot use OAuth connections!")]
AdminUserCannotOAuth,
AdminUserCannotHaveServiceConnection,

/// <summary>
/// Attempted to login with a disabled OAuth provider.
Expand Down Expand Up @@ -669,5 +669,11 @@ public enum ErrorCode : uint
/// </summary>
[Description("GraphQL swarm remote gateways not implemented!")]
RemoteGatewaysNotImplemented,

/// <summary>
/// Attempted to edit a user security property which is locked down due to the server being in <see cref="Response.ServerInformationResponse.OidcStrictMode"/>.
/// </summary>
[Description("One or more of the fields requested to be updated cannot be due to the server being in OIDC strict mode.")]
BadUserEditDueToOidcStrictMode,
}
}
5 changes: 5 additions & 0 deletions src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public abstract class UserApiBase : UserModelBase
/// </summary>
public ICollection<OAuthConnection>? OAuthConnections { get; set; }

/// <summary>
/// List of <see cref="OidcConnection"/>s associated with the user.
/// </summary>
public ICollection<OidcConnection>? OidcConnections { get; set; }

/// <summary>
/// The <see cref="Models.PermissionSet"/> directly associated with the user.
/// </summary>
Expand Down
10 changes: 7 additions & 3 deletions src/Tgstation.Server.Api/Models/OAuthProvider.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Newtonsoft.Json;
using System;

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Tgstation.Server.Api.Models
{
/// <summary>
/// List of OAuth providers supported by TGS.
/// List of OAuth2.0 providers supported by TGS that do not support OIDC.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum OAuthProvider
Expand All @@ -20,13 +22,15 @@ public enum OAuthProvider
Discord,

/// <summary>
/// https://tgstation13.org.
/// Pre-hostening https://tgstation13.org. No longer supported.
/// </summary>
[Obsolete("tgstation13.org no longer has a custom OAuth solution. This option will be removed in a future TGS version.", true)]
TGForums,

/// <summary>
/// https://www.keycloak.org.
/// </summary>
[Obsolete("This should now be implemented as an OIDC provider. This option will be removed in a future TGS version.")]
Keycloak,

/// <summary>
Expand Down
28 changes: 28 additions & 0 deletions src/Tgstation.Server.Api/Models/OidcConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;

using Newtonsoft.Json;

namespace Tgstation.Server.Api.Models
{
/// <summary>
/// Represents a valid OIDC connection.
/// </summary>
public class OidcConnection
{
/// <summary>
/// The <see cref="OidcProviderInfo.SchemeKey"/> of the <see cref="OidcConnection"/>.
/// </summary>]
[Required]
[RequestOptions(FieldPresence.Required)]
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
public string? SchemeKey { get; set; }

/// <summary>
/// The ID of the user in the OIDC proivder ("sub" claim).
/// </summary>
[Required]
[RequestOptions(FieldPresence.Required)]
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
public string? ExternalUserId { get; set; }
}
}
30 changes: 30 additions & 0 deletions src/Tgstation.Server.Api/Models/OidcProviderInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;

namespace Tgstation.Server.Api.Models
{
/// <summary>
/// Represents a configured OIDC provider.
/// </summary>
public sealed class OidcProviderInfo
{
/// <summary>
/// The scheme key used to reference the <see cref="OidcProviderInfo"/>. Unique amongst providers.
/// </summary>
public string? SchemeKey { get; set; }

/// <summary>
/// The provider's name as it should be displayed to the user.
/// </summary>
public string? FriendlyName { get; set; }

/// <summary>
/// Colour that should be used to theme this OIDC provider.
/// </summary>
public string? ThemeColour { get; set; }

/// <summary>
/// Image URL that should be used to theme this OIDC provider.
/// </summary>
public Uri? ThemeIconUrl { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public sealed class ServerInformationResponse : Internal.ServerInformationBase
/// </summary>
public Dictionary<OAuthProvider, OAuthProviderInfo>? OAuthProviderInfos { get; set; }

/// <summary>
/// List of configured <see cref="OidcProviderInfo"/>s.
/// </summary>
public List<OidcProviderInfo>? OidcProviderInfos { get; set; }

/// <summary>
/// If only OIDC logins and registration is allowed.
/// </summary>
public bool OidcStrictMode { get; set; }

/// <summary>
/// If there is a server update in progress.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Tgstation.Server.Api/Rights/AdministrationRights.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public enum AdministrationRights : ulong
DownloadLogs = 1 << 5,

/// <summary>
/// User can modify their own <see cref="Models.Internal.UserApiBase.OAuthConnections"/>.
/// User can modify their own <see cref="Models.Internal.UserApiBase.OAuthConnections"/> and <see cref="Models.Internal.UserApiBase.OidcConnections"/>.
/// </summary>
EditOwnOAuthConnections = 1 << 6,
EditOwnServiceConnections = 1 << 6,

/// <summary>
/// User can upgrade/downgrade TGS using file uploads through the API.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mutation CreateUserFromOAuthConnection($name: String!, $oAuthConnections: [OAuthConnectionInput!]!) {
createUserByOAuthAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) {
createUserByServiceConnectionAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) {
errors {
... on ErrorMessageError {
additionalData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mutation CreateUserGroup($name: String!) {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mutation CreateUserGroupWithInstanceListPerm($name: String!) {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mutation SetFullPermsOnUserGroup($id: ID!) {
administrationRights: {
canChangeVersion: true
canDownloadLogs: true
canEditOwnOAuthConnections: true
canEditOwnServiceConnections: true
canEditOwnPassword: true
canReadUsers: true
canWriteUsers: true
Expand Down Expand Up @@ -36,7 +36,7 @@ mutation SetFullPermsOnUserGroup($id: ID!) {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ mutation SetUserGroup($id: ID!, $newGroupId: ID!) {
user {
ownedPermissionSet {
instanceManagerRights {
canCreate
canDelete
canGrantPermissions
canList
canRead
canRelocate
canRename
canSetAutoUpdate
canSetChatBotLimit
canSetConfiguration
canSetOnline
canCreate
canDelete
canGrantPermissions
canList
canRead
canRelocate
canRename
canSetAutoUpdate
canSetChatBotLimit
canSetConfiguration
canSetOnline
}
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnPassword
canReadUsers
canRestartHost
canUploadVersion
canWriteUsers
canChangeVersion
canDownloadLogs
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
canUploadVersion
canWriteUsers
}
}
group {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand All @@ -37,7 +37,7 @@ mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ query GetSomeGroupInfo($id: ID!) {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ query ReadCurrentUser {
administrationRights {
canChangeVersion
canDownloadLogs
canEditOwnOAuthConnections
canEditOwnServiceConnections
canEditOwnPassword
canReadUsers
canRestartHost
Expand Down
10 changes: 9 additions & 1 deletion src/Tgstation.Server.Host/Authority/IUserAuthority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ public interface IUserAuthority : IAuthority
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuth.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);

/// <summary>
/// Gets the <see cref="GraphQL.Types.OAuth.OidcConnection"/>s for the <see cref="User"/> with a given <paramref name="userId"/>.
/// </summary>
/// <param name="userId">The <see cref="EntityId.Id"/> of the <see cref="User"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuth.OidcConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OidcConnection[]>> OidcConnections(long userId, CancellationToken cancellationToken);

/// <summary>
/// Gets all registered <see cref="User"/>s.
/// </summary>
Expand Down Expand Up @@ -70,7 +78,7 @@ ValueTask<AuthorityResponse<User>> Create(
/// <param name="updateRequest">The <see cref="UserUpdateRequest"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in am <see cref="AuthorityResponse{TResult}"/> for the created <see cref="User"/>.</returns>
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnOAuthConnections)]
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnServiceConnections)]
ValueTask<AuthorityResponse<User>> Update(UserUpdateRequest updateRequest, CancellationToken cancellationToken);
}
}
16 changes: 15 additions & 1 deletion src/Tgstation.Server.Host/Authority/LoginAuthority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
using Tgstation.Server.Host.Models;
Expand Down Expand Up @@ -58,6 +60,11 @@ sealed class LoginAuthority : AuthorityBase, ILoginAuthority
/// </summary>
readonly ISessionInvalidationTracker sessionInvalidationTracker;

/// <summary>
/// The <see cref="SecurityConfiguration"/> for the <see cref="LoginAuthority"/>.
/// </summary>
readonly SecurityConfiguration securityConfiguration;

/// <summary>
/// Generate an <see cref="AuthorityResponse{TResult}"/> for a given <paramref name="headersException"/>.
/// </summary>
Expand Down Expand Up @@ -106,6 +113,7 @@ static AuthorityResponse<TResult> GenerateHeadersExceptionResponse<TResult>(Head
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/>.</param>
/// <param name="identityCache">The value of <see cref="identityCache"/>.</param>
/// <param name="sessionInvalidationTracker">The value of <see cref="sessionInvalidationTracker"/>.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="securityConfiguration"/>.</param>
public LoginAuthority(
IAuthenticationContext authenticationContext,
IDatabaseContext databaseContext,
Expand All @@ -116,7 +124,8 @@ public LoginAuthority(
ITokenFactory tokenFactory,
ICryptographySuite cryptographySuite,
IIdentityCache identityCache,
ISessionInvalidationTracker sessionInvalidationTracker)
ISessionInvalidationTracker sessionInvalidationTracker,
IOptions<SecurityConfiguration> securityConfigurationOptions)
: base(
authenticationContext,
databaseContext,
Expand All @@ -129,11 +138,16 @@ public LoginAuthority(
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
this.sessionInvalidationTracker = sessionInvalidationTracker ?? throw new ArgumentNullException(nameof(sessionInvalidationTracker));
securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions));
}

/// <inheritdoc />
public async ValueTask<AuthorityResponse<LoginResult>> AttemptLogin(CancellationToken cancellationToken)
{
// password and oauth logins disabled
if (securityConfiguration.OidcStrictMode)
return Unauthorized<LoginResult>();

var headers = apiHeadersProvider.ApiHeaders;
if (headers == null)
return GenerateHeadersExceptionResponse<LoginResult>(apiHeadersProvider.HeadersException!);
Expand Down
Loading
Loading