Adds Oauth framework

This commit is contained in:
Jordan Brown
2020-11-22 21:11:31 -05:00
parent b6cab4e94b
commit 79700c7d0e
47 changed files with 4330 additions and 163 deletions
+4 -4
View File
@@ -3,10 +3,10 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="ControlPanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>4.6.0</TgsCoreVersion>
<TgsConfigVersion>2.1.1</TgsConfigVersion>
<TgsApiVersion>7.4.0</TgsApiVersion>
<TgsClientVersion>8.4.0</TgsClientVersion>
<TgsCoreVersion>4.7.0</TgsCoreVersion>
<TgsConfigVersion>2.2.0</TgsConfigVersion>
<TgsApiVersion>8.0.0</TgsApiVersion>
<TgsClientVersion>9.0.0</TgsClientVersion>
<TgsDmapiVersion>5.2.8</TgsDmapiVersion>
<TgsHostWatchdogVersion>1.1.0</TgsHostWatchdogVersion>
<TgsContainerScriptVersion>1.1.1</TgsContainerScriptVersion>
+48 -12
View File
@@ -8,6 +8,7 @@ using System.Net.Http.Headers;
using System.Net.Mime;
using System.Reflection;
using System.Text;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Api
{
@@ -17,25 +18,35 @@ namespace Tgstation.Server.Api
public sealed class ApiHeaders
{
/// <summary>
/// The <see cref="ApiVersion"/> header key
/// The <see cref="ApiVersion"/> header key.
/// </summary>
public const string ApiVersionHeader = "Api";
/// <summary>
/// The <see cref="InstanceId"/> header key
/// The <see cref="InstanceId"/> header key.
/// </summary>
public const string InstanceIdHeader = "Instance";
/// <summary>
/// The <see cref="OAuthProvider"/> header key.
/// </summary>
public const string OAuthProviderHeader = "OAuthProvider";
/// <summary>
/// The JWT authentication header scheme
/// </summary>
public const string JwtAuthenticationScheme = "bearer";
public const string BearerAuthenticationScheme = "bearer";
/// <summary>
/// The JWT authentication header scheme
/// </summary>
public const string BasicAuthenticationScheme = "basic";
/// <summary>
/// The JWT authentication header scheme
/// </summary>
public const string OAuthAuthenticationScheme = "oauth";
/// <summary>
/// The current <see cref="System.Reflection.AssemblyName"/>
/// </summary>
@@ -47,7 +58,7 @@ namespace Tgstation.Server.Api
public static readonly Version Version = AssemblyName.Version.Semver();
/// <summary>
/// The instance <see cref="Models.EntityId.Id"/> being accessed
/// The instance <see cref="EntityId.Id"/> being accessed
/// </summary>
public long? InstanceId { get; set; }
@@ -82,9 +93,14 @@ namespace Tgstation.Server.Api
public string? Password { get; }
/// <summary>
/// If the header uses password or JWT authentication
/// The <see cref="Models.OAuthProvider"/> the <see cref="Token"/> is for, if any.
/// </summary>
public bool IsTokenAuthentication => Token != null;
public OAuthProvider? OAuthProvider { get; }
/// <summary>
/// If the header uses password or TGS JWT authentication.
/// </summary>
public bool IsTokenAuthentication => Token != null && !OAuthProvider.HasValue;
/// <summary>
/// Checks if a given <paramref name="otherVersion"/> is compatible with our own
@@ -98,12 +114,15 @@ namespace Tgstation.Server.Api
/// </summary>
/// <param name="userAgent">The value of <see cref="UserAgent"/></param>
/// <param name="token">The value of <see cref="Token"/></param>
public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null)
/// <param name="oauthProvider">The value of <see cref="OAuthProvider"/>.</param>
public ApiHeaders(ProductHeaderValue userAgent, string token, OAuthProvider? oauthProvider = null) : this(userAgent, token, null, null)
{
if (userAgent == null)
throw new ArgumentNullException(nameof(userAgent));
if (token == null)
throw new ArgumentNullException(nameof(token));
OAuthProvider = oauthProvider;
}
/// <summary>
@@ -191,7 +210,20 @@ namespace Tgstation.Server.Api
switch (scheme.ToLowerInvariant())
#pragma warning restore CA1308 // Normalize strings to uppercase
{
case JwtAuthenticationScheme:
case OAuthAuthenticationScheme:
if (requestHeaders.Headers.TryGetValue(OAuthProviderHeader, out StringValues oauthProviderValues))
{
var oauthProviderString = oauthProviderValues.First();
if (Enum.TryParse<OAuthProvider>(oauthProviderString, out var oauthProvider))
OAuthProvider = oauthProvider;
else
AddError(HeaderTypes.OAuthProvider, "Invalid OAuth provider!");
}
else
AddError(HeaderTypes.OAuthProvider, $"Missing {OAuthProviderHeader} header!");
goto case BearerAuthenticationScheme;
case BearerAuthenticationScheme:
Token = parameter;
break;
case BasicAuthenticationScheme:
@@ -253,7 +285,7 @@ namespace Tgstation.Server.Api
/// Set <see cref="HttpRequestHeaders"/> using the <see cref="ApiHeaders"/>. This initially clears <paramref name="headers"/>
/// </summary>
/// <param name="headers">The <see cref="HttpRequestHeaders"/> to set</param>
/// <param name="instanceId">The instance <see cref="Models.EntityId.Id"/> for the request</param>
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> for the request</param>
public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
{
if (headers == null)
@@ -263,12 +295,16 @@ namespace Tgstation.Server.Api
headers.Clear();
headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
if (IsTokenAuthentication)
headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
else
if (!IsTokenAuthentication)
headers.Authorization = new AuthenticationHeaderValue(
BasicAuthenticationScheme,
Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
else
{
headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, Token);
if (OAuthProvider.HasValue)
headers.Add(OAuthProviderHeader, OAuthProvider.ToString());
}
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
+9 -4
View File
@@ -1,4 +1,4 @@
using System;
using System;
namespace Tgstation.Server.Api
{
@@ -24,13 +24,18 @@ namespace Tgstation.Server.Api
Accept = 2,
/// <summary>
/// Api header.
/// <see cref="ApiHeaders.ApiVersionHeader"/>.
/// </summary>
Api = 4,
/// <summary>
/// <see cref="Microsoft.Net.Http.Headers.HeaderNames.Authorization"/>
/// </summary>
Authorization = 8
Authorization = 8,
/// <summary>
/// <see cref="ApiHeaders.OAuthProviderHeader"/>.
/// </summary>
OAuthProvider,
}
}
}
@@ -582,5 +582,11 @@ namespace Tgstation.Server.Api.Models
/// </summary>
[Description("The requested port is either already in use by TGS or could not be allocated!")]
PortNotAvailable,
/// <summary>
/// Attempted to set <see cref="User.OAuthConnections"/> for the admin user.
/// </summary>
[Description("The admin user cannot use OAuth connections!")]
AdminUserCannotOAuth,
}
}
@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Api.Models
{
/// <summary>
/// Represents a valid OAuth connection.
/// </summary>
public class OAuthConnection
{
/// <summary>
/// The <see cref="OAuthProvider"/> of the <see cref="OAuthConnection"/>.
/// </summary>]
[EnumDataType(typeof(OAuthProvider))]
public OAuthProvider Provider { get; set; }
/// <summary>
/// The ID of the user in the <see cref="Provider"/>.
/// </summary>
public ulong ExternalUserId { get; set; }
}
}
@@ -0,0 +1,13 @@
namespace Tgstation.Server.Api.Models
{
/// <summary>
/// List of OAuth providers supported by TGS
/// </summary>
public enum OAuthProvider
{
/// <summary>
/// https://github.com
/// </summary>
GitHub,
}
}
+9 -2
View File
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Api.Models
using System.Collections.Generic;
namespace Tgstation.Server.Api.Models
{
/// <inheritdoc />
public class User : Internal.User
@@ -17,5 +19,10 @@
/// The <see cref="User"/> who created this <see cref="User"/>
/// </summary>
public Internal.User? CreatedBy { get; set; }
/// <summary>
/// List of <see cref="OAuthConnection"/>s associated with the <see cref="User"/>.
/// </summary>
public ICollection<OAuthConnection>? OAuthConnections { get; set; }
}
}
}
@@ -42,5 +42,10 @@ namespace Tgstation.Server.Api.Rights
/// User can list and download <see cref="Models.LogFile"/>s.
/// </summary>
DownloadLogs = 32,
/// <summary>
/// User can modify their own <see cref="Models.User.OAuthConnections"/>.
/// </summary>
EditOwnOAuthConnections = 64,
}
}
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "3.1.7",
"version": "3.1.10",
"commands": [
"dotnet-ef"
]
@@ -0,0 +1,18 @@
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
/// OAuth options for GitHub
/// </summary>
sealed class GitHubOAuthConfiguration
{
/// <summary>
/// The GitHub client ID.
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// The GitHub client secret.
/// </summary>
public string ClientSecret { get; set; }
}
}
@@ -1,3 +1,5 @@
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
@@ -15,6 +17,11 @@ namespace Tgstation.Server.Host.Configuration
/// </summary>
const uint DefaultTokenExpiryMinutes = 15;
/// <summary>
/// Default value of <see cref="OAuthTokenExpiryMinutes"/>.
/// </summary>
const uint DefaultOAuthTokenExpiryMinutes = 60 * 24; // 1 day
/// <summary>
/// Default value of <see cref="TokenClockSkewMinutes"/>.
/// </summary>
@@ -26,12 +33,12 @@ namespace Tgstation.Server.Host.Configuration
const uint DefaultTokenSigningKeyByteAmount = 256;
/// <summary>
/// Amount of minutes until generated <see cref="Api.Models.Token"/>s expire.
/// Amount of minutes until <see cref="Token"/>s generated from passwords expire.
/// </summary>
public uint TokenExpiryMinutes { get; set; } = DefaultTokenExpiryMinutes;
/// <summary>
/// Amount of minutes to skew the clock for <see cref="Api.Models.Token"/> validation.
/// Amount of minutes to skew the clock for <see cref="Token"/> validation.
/// </summary>
public uint TokenClockSkewMinutes { get; set; } = DefaultTokenClockSkewMinutes;
@@ -40,9 +47,19 @@ namespace Tgstation.Server.Host.Configuration
/// </summary>
public uint TokenSigningKeyByteCount { get; set; } = DefaultTokenSigningKeyByteAmount;
/// <summary>
/// Amount of minutes until <see cref="Token"/>s generated from OAuth logins expire.
/// </summary>
public uint OAuthTokenExpiryMinutes { get; set; } = DefaultOAuthTokenExpiryMinutes;
/// <summary>
/// A custom token signing key. Overrides <see cref="TokenSigningKeyByteCount"/>.
/// </summary>
public string CustomTokenSigningKeyBase64 { get; set; }
/// <summary>
/// OAuth options for GitHub.
/// </summary>
public GitHubOAuthConfiguration GitHubOAuth { get; set; }
}
}
@@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Octokit;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@@ -115,14 +113,6 @@ namespace Tgstation.Server.Host.Controllers
fileLoggingConfiguration = fileLoggingConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
}
ObjectResult RateLimit(RateLimitExceededException exception)
{
Logger.LogWarning(exception, "Exceeded GitHub rate limit!");
var secondsString = Math.Ceiling((exception.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture);
Response.Headers.Add("Retry-After", new StringValues(secondsString));
return StatusCode(HttpStatusCode.TooManyRequests, new ErrorMessage(ErrorCode.GitHubApiRateLimit));
}
/// <summary>
/// Try to download and apply an update with a given <paramref name="newVersion"/>.
/// </summary>
@@ -2,8 +2,11 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Octokit;
using Serilog.Context;
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Mime;
@@ -60,7 +63,7 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="logger">The value of <see cref="Logger"/></param>
/// <param name="requireHeaders">The value of <see cref="requireHeaders"/></param>
public ApiController(
protected ApiController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
ILogger<ApiController> logger,
@@ -115,6 +118,22 @@ namespace Tgstation.Server.Host.Controllers
/// <returns>A <see cref="HttpStatusCode.Created"/> <see cref="ObjectResult"/> with the given <paramref name="payload"/>.</returns>
protected ObjectResult Created(object payload) => StatusCode((int)HttpStatusCode.Created, payload);
/// <summary>
/// 429 response for a given <paramref name="rateLimitException"/>.
/// </summary>
/// <param name="rateLimitException">The <see cref="RateLimitExceededException"/> that occurred.</param>
/// <returns>A <see cref="HttpStatusCode.TooManyRequests"/> <see cref="ObjectResult"/>.</returns>
protected ObjectResult RateLimit(RateLimitExceededException rateLimitException)
{
if (rateLimitException == null)
throw new ArgumentNullException(nameof(rateLimitException));
Logger.LogWarning(rateLimitException, "Exceeded GitHub rate limit!");
var secondsString = Math.Ceiling((rateLimitException.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture);
Response.Headers.Add(HeaderNames.RetryAfter, secondsString);
return StatusCode(HttpStatusCode.TooManyRequests, new ErrorMessage(ErrorCode.GitHubApiRateLimit));
}
/// <summary>
/// Performs validation a request.
/// </summary>
@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Octokit;
using System;
using System.Linq;
using System.Threading;
@@ -17,6 +18,7 @@ using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Security.OAuth;
using Tgstation.Server.Host.System;
using Wangkanai.Detection;
@@ -53,6 +55,11 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IIdentityCache identityCache;
/// <summary>
/// The <see cref="IOAuthProviders"/> for the <see cref="HomeController"/>.
/// </summary>
readonly IOAuthProviders oAuthProviders;
/// <summary>
/// The <see cref="IBrowserResolver"/> for the <see cref="HomeController"/>
/// </summary>
@@ -78,6 +85,7 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/></param>
/// <param name="identityCache">The value of <see cref="identityCache"/></param>
/// <param name="oAuthProviders">The value of <see cref="oAuthProviders"/>.</param>
/// <param name="browserResolver">The value of <see cref="browserResolver"/></param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="controlPanelConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="controlPanelConfiguration"/></param>
@@ -90,6 +98,7 @@ namespace Tgstation.Server.Host.Controllers
ICryptographySuite cryptographySuite,
IAssemblyInformationProvider assemblyInformationProvider,
IIdentityCache identityCache,
IOAuthProviders oAuthProviders,
IBrowserResolver browserResolver,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
@@ -106,6 +115,7 @@ namespace Tgstation.Server.Host.Controllers
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
this.oAuthProviders = oAuthProviders ?? throw new ArgumentNullException(nameof(oAuthProviders));
this.browserResolver = browserResolver;
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
@@ -140,7 +150,7 @@ namespace Tgstation.Server.Host.Controllers
if (controlPanelConfiguration.Enable && browserResolver.Browser.Type != BrowserType.Generic)
{
Logger.LogDebug("Unauthorized browser request (User-Agent: \"{0}\"), redirecting to control panel...", browserResolver.UserAgent);
return Redirect(Application.ControlPanelRoute);
return Redirect(Core.Application.ControlPanelRoute);
}
return ApiHeaders == null ? HeadersIssue() : Unauthorized();
@@ -168,26 +178,56 @@ namespace Tgstation.Server.Host.Controllers
if (ApiHeaders.IsTokenAuthentication)
return BadRequest(new ErrorMessage(ErrorCode.TokenWithToken));
ISystemIdentity systemIdentity;
try
{
// trust the system over the database because a user's name can change while still having the same SID
systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
}
catch (NotImplementedException)
{
systemIdentity = null;
}
var oAuthLogin = ApiHeaders.OAuthProvider.HasValue;
ISystemIdentity systemIdentity = null;
if (!oAuthLogin)
try
{
// trust the system over the database because a user's name can change while still having the same SID
systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
}
catch (NotImplementedException ex)
{
Logger.LogTrace(ex, "System identities not implemented!");
}
using (systemIdentity)
{
// Get the user from the database
IQueryable<Models.User> query = DatabaseContext.Users.AsQueryable();
string canonicalName = Models.User.CanonicalizeName(ApiHeaders.Username);
if (systemIdentity == null)
query = query.Where(x => x.CanonicalName == canonicalName);
if (oAuthLogin)
{
ulong? externalUserId;
try
{
externalUserId = await oAuthProviders
.GetValidator(ApiHeaders.OAuthProvider.Value)
.ValidateResponseCode(ApiHeaders.Token, cancellationToken)
.ConfigureAwait(false);
}
catch (RateLimitExceededException ex)
{
return RateLimit(ex);
}
if (!externalUserId.HasValue)
return Unauthorized();
query = query.Where(
x => x.OAuthConnections.Any(
y => y.Provider == ApiHeaders.OAuthProvider.Value
&& y.ExternalUserId == externalUserId.Value));
}
else
query = query.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
{
string canonicalName = Models.User.CanonicalizeName(ApiHeaders.Username);
if (systemIdentity == null)
query = query.Where(x => x.CanonicalName == canonicalName);
else
query = query.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
}
var users = await query.Select(x => new Models.User
{
Id = x.Id,
@@ -213,32 +253,33 @@ namespace Tgstation.Server.Host.Controllers
var originalHash = user.PasswordHash;
var isDbUser = originalHash != null;
bool usingSystemIdentity = systemIdentity != null && !isDbUser;
if (!usingSystemIdentity)
{
// DB User password check and update
if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
return Unauthorized();
if (user.PasswordHash != originalHash)
if (!oAuthLogin)
if (!usingSystemIdentity)
{
Logger.LogDebug("User ID {0}'s password hash needs a refresh, updating database.", user.Id);
var updatedUser = new Models.User
// DB User password check and update
if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
return Unauthorized();
if (user.PasswordHash != originalHash)
{
Id = user.Id
};
DatabaseContext.Users.Attach(updatedUser);
updatedUser.PasswordHash = user.PasswordHash;
Logger.LogDebug("User ID {0}'s password hash needs a refresh, updating database.", user.Id);
var updatedUser = new Models.User
{
Id = user.Id
};
DatabaseContext.Users.Attach(updatedUser);
updatedUser.PasswordHash = user.PasswordHash;
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
}
}
else if (systemIdentity.Username != user.Name)
{
// System identity username change update
Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id);
DatabaseContext.Users.Attach(user);
user.Name = systemIdentity.Username;
user.CanonicalName = Models.User.CanonicalizeName(user.Name);
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
}
}
else if (systemIdentity.Username != user.Name)
{
// System identity username change update
Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id);
DatabaseContext.Users.Attach(user);
user.Name = systemIdentity.Username;
user.CanonicalName = Models.User.CanonicalizeName(user.Name);
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
}
// Now that the bookeeping is done, tell them to fuck off if necessary
if (!user.Enabled.Value)
@@ -247,7 +288,7 @@ namespace Tgstation.Server.Host.Controllers
return Forbid();
}
var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false);
var token = await tokenFactory.CreateToken(user, oAuthLogin, cancellationToken).ConfigureAwait(false);
if (usingSystemIdentity)
{
// expire the identity slightly after the auth token in case of lag
@@ -262,6 +303,6 @@ namespace Tgstation.Server.Host.Controllers
return Json(token);
}
}
#pragma warning restore CA1506
#pragma warning restore CA1506
}
}
@@ -281,7 +281,9 @@ namespace Tgstation.Server.Host.Controllers
}
}
#pragma warning disable CA1508 // Avoid dead conditional code
if (earlyOut != null)
#pragma warning restore CA1508 // Avoid dead conditional code
return earlyOut;
// Last test, ensure it's in the list of valid paths
@@ -420,9 +422,9 @@ namespace Tgstation.Server.Host.Controllers
var moveJob = await InstanceQuery()
.SelectMany(x => x.Jobs).
#pragma warning disable CA1307 // Specify StringComparison
#pragma warning disable CA1310 // Specify StringComparison
Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
#pragma warning restore CA1307 // Specify StringComparison
#pragma warning restore CA1310 // Specify StringComparison
.Select(x => new Models.Job
{
Id = x.Id
@@ -614,9 +616,9 @@ namespace Tgstation.Server.Host.Controllers
var moveJobs = await GetBaseQuery()
.SelectMany(x => x.Jobs)
#pragma warning disable CA1307 // Specify StringComparison
#pragma warning disable CA1310 // Specify StringComparison
.Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
#pragma warning restore CA1307 // Specify StringComparison
#pragma warning restore CA1310 // Specify StringComparison
.Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
.Include(x => x.Instance)
.ToListAsync(cancellationToken)
@@ -687,9 +689,9 @@ namespace Tgstation.Server.Host.Controllers
var moveJob = await QueryForUser()
.SelectMany(x => x.Jobs)
#pragma warning disable CA1307 // Specify StringComparison
#pragma warning disable CA1310 // Specify StringComparison
.Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
#pragma warning restore CA1307 // Specify StringComparison
#pragma warning restore CA1310 // Specify StringComparison
.Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
@@ -426,7 +426,9 @@ namespace Tgstation.Server.Host.Controllers
|| (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
return Forbid();
if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
#pragma warning disable CA1508 // Avoid dead conditional code
if (model.AccessToken?.Length == 0 && model.AccessUser?.Length == 0)
#pragma warning restore CA1508 // Avoid dead conditional code
{
// setting an empty string clears everything
currentModel.AccessUser = null;
@@ -74,7 +74,9 @@ namespace Tgstation.Server.Host.Controllers
BadRequestObjectResult CheckValidName(UserUpdate model, bool newUser)
{
var userInvalidWithNullName = newUser && model.Name == null && model.SystemIdentifier == null;
#pragma warning disable CA1508 // https://github.com/dotnet/roslyn-analyzers/issues/3685
if (userInvalidWithNullName || (model.Name != null && String.IsNullOrWhiteSpace(model.Name)))
#pragma warning restore CA1508
return BadRequest(new ErrorMessage(ErrorCode.UserMissingName));
model.Name = model.Name?.Trim();
@@ -118,6 +120,9 @@ namespace Tgstation.Server.Host.Controllers
if (model == null)
throw new ArgumentNullException(nameof(model));
if (model.OAuthConnections?.Any(x => x == null) == true)
return BadRequest(new ErrorMessage(ErrorCode.ModelValidationFailure));
if (!(model.Password == null ^ model.SystemIdentifier == null))
return BadRequest(new ErrorMessage(ErrorCode.UserMismatchPasswordSid));
@@ -132,17 +137,7 @@ namespace Tgstation.Server.Host.Controllers
if (fail != null)
return fail;
var dbUser = new Models.User
{
AdministrationRights = RightsHelper.Clamp(model.AdministrationRights ?? AdministrationRights.None),
CreatedAt = DateTimeOffset.Now,
CreatedBy = AuthenticationContext.User,
Enabled = model.Enabled ?? false,
InstanceManagerRights = RightsHelper.Clamp(model.InstanceManagerRights ?? InstanceManagerRights.None),
Name = model.Name,
SystemIdentifier = model.SystemIdentifier,
InstanceUsers = new List<Models.InstanceUser>()
};
var dbUser = CreateNewUserFromModel(model);
if (model.SystemIdentifier != null)
try
@@ -157,7 +152,7 @@ namespace Tgstation.Server.Host.Controllers
{
return RequiresPosixSystemIdentity();
}
else
else if (!(model.Password?.Length == 0 && model.OAuthConnections.Count != 0))
{
var result = TrySetPassword(dbUser, model.Password, true);
if (result != null)
@@ -170,6 +165,8 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
Logger.LogInformation("Created new user {0} ({1})", dbUser.Name, dbUser.Id);
return Created(dbUser.ToApi(true));
}
@@ -182,7 +179,7 @@ namespace Tgstation.Server.Host.Controllers
/// <response code="200"><see cref="Api.Models.User"/> updated successfully.</response>
/// <response code="404">Requested <see cref="Api.Models.Internal.User.Id"/> does not exist.</response>
[HttpPost]
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword)]
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnOAuthConnections)]
[ProducesResponseType(typeof(Api.Models.User), 200)]
[ProducesResponseType(typeof(ErrorMessage), 404)]
#pragma warning disable CA1502 // TODO: Decomplexify
@@ -192,19 +189,22 @@ namespace Tgstation.Server.Host.Controllers
if (model == null)
throw new ArgumentNullException(nameof(model));
if (!model.Id.HasValue)
if (!model.Id.HasValue || model.OAuthConnections?.Any(x => x == null) == true)
return BadRequest(new ErrorMessage(ErrorCode.ModelValidationFailure));
var callerAdministrationRights = (AdministrationRights)AuthenticationContext.GetRight(RightsType.Administration);
var passwordEditOnly = !callerAdministrationRights.HasFlag(AdministrationRights.WriteUsers);
var canEditAllUsers = callerAdministrationRights.HasFlag(AdministrationRights.WriteUsers);
var passwordEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnPassword);
var oAuthEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnOAuthConnections);
var originalUser = passwordEditOnly
var originalUser = passwordEdit
? AuthenticationContext.User
: await DatabaseContext
.Users
.AsQueryable()
.Where(x => x.Id == model.Id)
.Include(x => x.CreatedBy)
.Include(x => x.OAuthConnections)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
@@ -214,13 +214,15 @@ namespace Tgstation.Server.Host.Controllers
if (originalUser.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return Forbid();
// Ensure they are only trying to edit password (system identity change will trigger a bad request)
if (passwordEditOnly
// Ensure they are only trying to edit things they have perms for (system identity change will trigger a bad request)
if ((!canEditAllUsers
&& (model.Id != originalUser.Id
|| model.InstanceManagerRights.HasValue
|| model.AdministrationRights.HasValue
|| model.Enabled.HasValue
|| model.Name != null))
|| (!passwordEdit && model.Password != null)
|| (!oAuthEdit && model.OAuthConnections != null))
return Forbid();
if (model.SystemIdentifier != null && model.SystemIdentifier != originalUser.SystemIdentifier)
@@ -246,6 +248,22 @@ namespace Tgstation.Server.Host.Controllers
originalUser.Enabled = model.Enabled.Value;
}
if (model.OAuthConnections != null
&& (model.OAuthConnections.Count != originalUser.OAuthConnections.Count
|| !model.OAuthConnections.All(x => originalUser.OAuthConnections.Any(y => y.Provider == x.Provider && y.ExternalUserId == x.ExternalUserId))))
{
if (originalUser.CanonicalName == Models.User.CanonicalizeName(Api.Models.User.AdminName))
return BadRequest(new ErrorMessage(ErrorCode.AdminUserCannotOAuth));
originalUser.OAuthConnections.Clear();
foreach (var updatedConnection in model.OAuthConnections)
originalUser.OAuthConnections.Add(new Models.OAuthConnection
{
Provider = updatedConnection.Provider,
ExternalUserId = updatedConnection.ExternalUserId
});
}
var fail = CheckValidName(model, false);
if (fail != null)
return fail;
@@ -254,6 +272,8 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
Logger.LogInformation("Updated user {0} ({1})", originalUser.Name, originalUser.Id);
// return id only if not a self update and cannot read users
return Json(
model.Id == originalUser.Id
@@ -293,6 +313,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.CanonicalName != Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
.Include(x => x.CreatedBy)
.Include(x => x.OAuthConnections)
.ToListAsync(cancellationToken).ConfigureAwait(false);
return Json(users.Select(x => x.ToApi(true)));
}
@@ -321,6 +342,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.Id == id)
.Include(x => x.CreatedBy)
.Include(x => x.OAuthConnections)
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (user == default)
return NotFound();
@@ -330,5 +352,30 @@ namespace Tgstation.Server.Host.Controllers
return Json(user.ToApi(true));
}
/// <summary>
/// Creates a new <see cref="User"/> from a given <paramref name="model"/>.
/// </summary>
/// <param name="model">The <see cref="Api.Models.User"/> to use as a template.</param>
/// <returns>A new <see cref="User"/>.</returns>
Models.User CreateNewUserFromModel(Api.Models.User model) => new Models.User
{
AdministrationRights = RightsHelper.Clamp(model.AdministrationRights ?? AdministrationRights.None),
CreatedAt = DateTimeOffset.Now,
CreatedBy = AuthenticationContext.User,
Enabled = model.Enabled ?? false,
InstanceManagerRights = RightsHelper.Clamp(model.InstanceManagerRights ?? InstanceManagerRights.None),
Name = model.Name,
SystemIdentifier = model.SystemIdentifier,
InstanceUsers = new List<Models.InstanceUser>(),
OAuthConnections = model
.OAuthConnections
?.Select(x => new Models.OAuthConnection
{
Provider = x.Provider,
ExternalUserId = x.ExternalUserId
})
.ToList()
};
}
}
+24 -18
View File
@@ -37,6 +37,7 @@ using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Properties;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Security.OAuth;
using Tgstation.Server.Host.Setup;
using Tgstation.Server.Host.System;
@@ -100,7 +101,6 @@ namespace Tgstation.Server.Host.Core
// configure configuration
services.UseStandardConfig<UpdatesConfiguration>(Configuration);
services.UseStandardConfig<ControlPanelConfiguration>(Configuration);
services.UseStandardConfig<SecurityConfiguration>(Configuration);
// enable options which give us config reloading
services.AddOptions();
@@ -159,24 +159,29 @@ namespace Tgstation.Server.Host.Core
});
// configure bearer token validation
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions =>
{
// this line isn't actually run until the first request is made
// at that point tokenFactory will be populated
jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters;
jwtBearerOptions.Events = new JwtBearerEvents
var authenticationBuilder = services
.AddAuthentication(options =>
{
// Application is our composition root so this monstrosity of a line is okay
// At least, that's what I tell myself to sleep at night
OnTokenValidated = ctx => ctx
.HttpContext
.RequestServices
.GetRequiredService<IClaimsInjector>()
.InjectClaimsIntoContext(
ctx,
ctx.HttpContext.RequestAborted)
};
});
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtBearerOptions =>
{
// this line isn't actually run until the first request is made
// at that point tokenFactory will be populated
jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters;
jwtBearerOptions.Events = new JwtBearerEvents
{
// Application is our composition root so this monstrosity of a line is okay
// At least, that's what I tell myself to sleep at night
OnTokenValidated = ctx => ctx
.HttpContext
.RequestServices
.GetRequiredService<IClaimsInjector>()
.InjectClaimsIntoContext(
ctx,
ctx.HttpContext.RequestAborted)
};
});
// WARNING: STATIC CODE
// fucking prevents converting 'sub' to M$ bs
@@ -260,6 +265,7 @@ namespace Tgstation.Server.Host.Core
// configure security services
services.AddScoped<IAuthenticationContextFactory, AuthenticationContextFactory>();
services.AddScoped<IClaimsInjector, ClaimsInjector>();
services.AddScoped<IOAuthProviders, OAuthProviders>();
services.AddSingleton<IIdentityCache, IdentityCache>();
services.AddSingleton<ICryptographySuite, CryptographySuite>();
services.AddSingleton<ITokenFactory, TokenFactory>();
@@ -176,7 +176,7 @@ namespace Tgstation.Server.Host.Core
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Name = HeaderNames.Authorization,
Scheme = ApiHeaders.JwtAuthenticationScheme
Scheme = ApiHeaders.BearerAuthenticationScheme
});
}
@@ -87,10 +87,15 @@ namespace Tgstation.Server.Host.Database
public DbSet<TestMerge> TestMerges { get; set; }
/// <summary>
/// The <see cref="RevInfoTestMerge"/>s om the <see cref="DatabaseContext"/>
/// The <see cref="RevInfoTestMerge"/>s in the <see cref="DatabaseContext"/>
/// </summary>
public DbSet<RevInfoTestMerge> RevInfoTestMerges { get; set; }
/// <summary>
/// The <see cref="OAuthConnection"/>s in the <see cref="DatabaseContext"/>
/// </summary>
public DbSet<OAuthConnection> OAuthConnections { get; set; }
/// <summary>
/// The <see cref="DeleteBehavior"/> for the <see cref="CompileJob"/>/<see cref="RevisionInformation"/> foreign key.
/// </summary>
@@ -132,6 +137,9 @@ namespace Tgstation.Server.Host.Database
/// <inheritdoc />
IDatabaseCollection<ReattachInformation> IDatabaseContext.ReattachInformations => reattachInformationsCollection;
/// <inheritdoc />
IDatabaseCollection<OAuthConnection> IDatabaseContext.OAuthConnections => oAuthConnections;
/// <summary>
/// Backing field for <see cref="IDatabaseContext.Users"/>.
/// </summary>
@@ -192,6 +200,11 @@ namespace Tgstation.Server.Host.Database
/// </summary>
readonly IDatabaseCollection<ReattachInformation> reattachInformationsCollection;
/// <summary>
/// Backing field for <see cref="IDatabaseContext.OAuthConnections"/>.
/// </summary>
readonly IDatabaseCollection<OAuthConnection> oAuthConnections;
/// <summary>
/// Gets the configure action for a given <typeparamref name="TDatabaseContext"/>.
/// </summary>
@@ -216,7 +229,7 @@ namespace Tgstation.Server.Host.Database
/// Construct a <see cref="DatabaseContext"/>
/// </summary>
/// <param name="dbContextOptions">The <see cref="DbContextOptions"/> for the <see cref="DatabaseContext"/>.</param>
public DatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions)
protected DatabaseContext(DbContextOptions dbContextOptions) : base(dbContextOptions)
{
usersCollection = new DatabaseCollection<User>(Users);
instancesCollection = new DatabaseCollection<Instance>(Instances);
@@ -230,6 +243,7 @@ namespace Tgstation.Server.Host.Database
revisionInformationsCollection = new DatabaseCollection<RevisionInformation>(RevisionInformations);
jobsCollection = new DatabaseCollection<Job>(Jobs);
reattachInformationsCollection = new DatabaseCollection<ReattachInformation>(ReattachInformations);
oAuthConnections = new DatabaseCollection<OAuthConnection>(OAuthConnections);
}
/// <inheritdoc />
@@ -244,6 +258,9 @@ namespace Tgstation.Server.Host.Database
userModel.HasIndex(x => x.CanonicalName).IsUnique();
userModel.HasIndex(x => x.SystemIdentifier).IsUnique();
userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict);
userModel.HasMany(x => x.OAuthConnections).WithOne(x => x.User).OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<OAuthConnection>().HasIndex(x => new { x.Provider, x.ExternalUserId }).IsUnique();
modelBuilder.Entity<InstanceUser>().HasIndex(x => new { x.UserId, x.InstanceId }).IsUnique();
@@ -316,6 +333,7 @@ namespace Tgstation.Server.Host.Database
}
/// <inheritdoc />
#pragma warning disable CA1502 // Cyclomatic complexity
public async Task SchemaDowngradeForServerVersion(
ILogger<DatabaseContext> logger,
Version version,
@@ -338,7 +356,43 @@ namespace Tgstation.Server.Host.Database
if (version < new Version(4, 1, 0))
throw new NotSupportedException("Cannot migrate below version 4.1.0!");
if(version < new Version(4, 4, 0))
if (version < new Version(4, 6, 0))
switch (currentDatabaseType)
{
case DatabaseType.MariaDB:
case DatabaseType.MySql:
targetMigration = nameof(MYAddAdditionalDDParameters);
break;
case DatabaseType.PostgresSql:
targetMigration = nameof(PGAddAdditionalDDParameters);
break;
case DatabaseType.SqlServer:
case DatabaseType.Sqlite:
targetMigration = nameof(MSAddAdditionalDDParameters);
break;
default:
throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
}
if (version < new Version(4, 5, 0))
switch (currentDatabaseType)
{
case DatabaseType.MariaDB:
case DatabaseType.MySql:
targetMigration = nameof(MYAddDeploymentColumns);
break;
case DatabaseType.PostgresSql:
targetMigration = nameof(PGAddDeploymentColumns);
break;
case DatabaseType.SqlServer:
case DatabaseType.Sqlite:
targetMigration = nameof(MSAddDeploymentColumns);
break;
default:
throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
}
if (version < new Version(4, 4, 0))
switch (currentDatabaseType)
{
case DatabaseType.MariaDB:
@@ -403,5 +457,6 @@ namespace Tgstation.Server.Host.Database
logger.LogCritical(e, "Failed to migrate!");
}
}
#pragma warning restore CA1502 // Cyclomatic complexity
}
}
@@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
@@ -14,65 +14,70 @@ namespace Tgstation.Server.Host.Database
public interface IDatabaseContext
{
/// <summary>
/// The <see cref="User"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="User"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<User> Users { get; }
/// <summary>
/// The <see cref="Instance"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="Instance"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<Instance> Instances { get; }
/// <summary>
/// The <see cref="InstanceUser"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="InstanceUser"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<InstanceUser> InstanceUsers { get; }
/// <summary>
/// The <see cref="Job"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="Job"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<Job> Jobs { get; }
/// <summary>
/// The <see cref="CompileJob"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="CompileJob"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<CompileJob> CompileJobs { get; }
/// <summary>
/// The <see cref="RevisionInformation"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="RevisionInformation"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<RevisionInformation> RevisionInformations { get; }
/// <summary>
/// The <see cref="Models.DreamMakerSettings"/> in the <see cref="IDatabaseContext"/>
/// The <see cref="Models.DreamMakerSettings"/> in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<DreamMakerSettings> DreamMakerSettings { get; }
/// <summary>
/// The <see cref="Models.DreamDaemonSettings"/> in the <see cref="IDatabaseContext"/>
/// The <see cref="Models.DreamDaemonSettings"/> in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<DreamDaemonSettings> DreamDaemonSettings { get; }
/// <summary>
/// The <see cref="ChatBot"/>s in the <see cref="IDatabaseContext"/>
/// The <see cref="ChatBot"/>s in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<ChatBot> ChatBots { get; }
/// <summary>
/// The <see cref="ChatChannel"/> in the <see cref="IDatabaseContext"/>
/// The <see cref="ChatChannel"/> in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<ChatChannel> ChatChannels { get; }
/// <summary>
/// The <see cref="Models.RepositorySettings"/> in the <see cref="IDatabaseContext"/>
/// The <see cref="Models.RepositorySettings"/> in the <see cref="IDatabaseContext"/>.
/// </summary>
IDatabaseCollection<RepositorySettings> RepositorySettings { get; }
/// <summary>
/// The <see cref="DbSet{TEntity}"/> for <see cref="ReattachInformation"/>s
/// The <see cref="DbSet{TEntity}"/> for <see cref="ReattachInformation"/>s.
/// </summary>
IDatabaseCollection<ReattachInformation> ReattachInformations { get; }
/// <summary>
/// The <see cref="DbSet{TEntity}"/> for <see cref="OAuthConnection"/>s.
/// </summary>
IDatabaseCollection<OAuthConnection> OAuthConnections { get; }
/// <summary>
/// Saves changes made to the <see cref="IDatabaseContext"/>
/// </summary>
@@ -0,0 +1,820 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20201122231219_MSAddOAuthConnections")]
partial class MSAddOAuthConnections
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ChannelLimit")
.HasColumnType("int");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("bit");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long>("ReconnectionInterval")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
b.Property<decimal?>("DiscordChannelId")
.HasColumnType("decimal(20,0)");
b.Property<string>("IrcChannel")
.HasColumnType("nvarchar(100)")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("bit");
b.Property<string>("Tag")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("ChatSettingsId", "DiscordChannelId")
.IsUnique()
.HasFilter("[DiscordChannelId] IS NOT NULL");
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique()
.HasFilter("[IrcChannel] IS NOT NULL");
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("int");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("uniqueidentifier");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int?>("GitHubDeploymentId")
.HasColumnType("int");
b.Property<long?>("GitHubRepoId")
.HasColumnType("bigint");
b.Property<long>("JobId")
.HasColumnType("bigint");
b.Property<int?>("MinimumSecurityLevel")
.HasColumnType("int");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId")
.IsUnique();
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("bit");
b.Property<long>("HeartbeatSeconds")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int>("SecurityLevel")
.HasColumnType("int");
b.Property<long>("StartupTimeout")
.HasColumnType("bigint");
b.Property<long>("TopicRequestTimeout")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiValidationPort")
.HasColumnType("int");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("int");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
.HasColumnType("bit");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
b.Property<int>("ChatBotLimit")
.HasColumnType("int");
b.Property<int>("ConfigurationType")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("bit");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ByondRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("ChatBotRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("ConfigurationRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("DreamDaemonRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("DreamMakerRights")
.HasColumnType("decimal(20,0)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<decimal>("InstanceUserRights")
.HasColumnType("decimal(20,0)");
b.Property<decimal>("RepositoryRights")
.HasColumnType("decimal(20,0)");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("UserId", "InstanceId")
.IsUnique();
b.ToTable("InstanceUsers");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal?>("CancelRight")
.HasColumnType("decimal(20,0)");
b.Property<decimal?>("CancelRightsType")
.HasColumnType("decimal(20,0)");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("bit");
b.Property<long?>("CancelledById")
.HasColumnType("bigint");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long?>("ErrorCode")
.HasColumnType("bigint");
b.Property<string>("ExceptionDetails")
.HasColumnType("nvarchar(max)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("datetimeoffset");
b.Property<long>("StartedById")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("datetimeoffset");
b.HasKey("Id");
b.HasIndex("CancelledById");
b.HasIndex("InstanceId");
b.HasIndex("StartedById");
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ExternalUserId")
.HasColumnType("decimal(20,0)");
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("int");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int>("ProcessId")
.HasColumnType("int");
b.Property<int>("RebootState")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessToken")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("bit");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
.HasColumnType("bit");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("bit");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("bit");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.Property<long>("TestMergeId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("RevisionInformationId");
b.HasIndex("TestMergeId");
b.ToTable("RevInfoTestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Author")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Comment")
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetimeoffset");
b.Property<long>("MergedById")
.HasColumnType("bigint");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("bigint");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("nvarchar(40)")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("MergedById");
b.HasIndex("PrimaryRevisionInformationId")
.IsUnique();
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("AdministrationRights")
.HasColumnType("decimal(20,0)");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("datetimeoffset");
b.Property<long?>("CreatedById")
.HasColumnType("bigint");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("bit");
b.Property<decimal>("InstanceManagerRights")
.HasColumnType("decimal(20,0)");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("datetimeoffset");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("SystemIdentifier")
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique()
.HasFilter("[SystemIdentifier] IS NOT NULL");
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
.WithOne()
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamDaemonSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamMakerSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
.WithMany()
.HasForeignKey("CancelledById");
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("Jobs")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("RepositorySettings")
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the OAuthConnections table for MSSQL.
/// </summary>
public partial class MSAddOAuthConnections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.CreateTable(
name: "OAuthConnections",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Provider = table.Column<int>(nullable: false),
ExternalUserId = table.Column<decimal>(nullable: false),
UserId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthConnections", x => x.Id);
table.ForeignKey(
name: "FK_OAuthConnections_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_UserId",
table: "OAuthConnections",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_Provider_ExternalUserId",
table: "OAuthConnections",
columns: new[] { "Provider", "ExternalUserId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "OAuthConnections");
}
}
}
@@ -0,0 +1,809 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20201122231327_MYAddOAuthConnections")]
partial class MYAddOAuthConnections
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ushort?>("ChannelLimit")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("tinyint(1)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<uint?>("ReconnectionInterval")
.IsRequired()
.HasColumnType("int unsigned");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
b.Property<ulong?>("DiscordChannelId")
.HasColumnType("bigint unsigned");
b.Property<string>("IrcChannel")
.HasColumnType("varchar(100) CHARACTER SET utf8mb4")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<string>("Tag")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("ChatSettingsId", "DiscordChannelId")
.IsUnique();
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique();
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("int");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("int");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("char(36)");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int?>("GitHubDeploymentId")
.HasColumnType("int");
b.Property<long?>("GitHubRepoId")
.HasColumnType("bigint");
b.Property<long>("JobId")
.HasColumnType("bigint");
b.Property<int?>("MinimumSecurityLevel")
.HasColumnType("int");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId")
.IsUnique();
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<uint?>("HeartbeatSeconds")
.IsRequired()
.HasColumnType("int unsigned");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<int>("SecurityLevel")
.HasColumnType("int");
b.Property<uint?>("StartupTimeout")
.IsRequired()
.HasColumnType("int unsigned");
b.Property<uint?>("TopicRequestTimeout")
.IsRequired()
.HasColumnType("int unsigned");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ushort?>("ApiValidationPort")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("int");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<uint?>("AutoUpdateInterval")
.IsRequired()
.HasColumnType("int unsigned");
b.Property<ushort?>("ChatBotLimit")
.IsRequired()
.HasColumnType("smallint unsigned");
b.Property<int>("ConfigurationType")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ulong>("ByondRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("ChatBotRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("ConfigurationRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("DreamDaemonRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("DreamMakerRights")
.HasColumnType("bigint unsigned");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<ulong>("InstanceUserRights")
.HasColumnType("bigint unsigned");
b.Property<ulong>("RepositoryRights")
.HasColumnType("bigint unsigned");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("UserId", "InstanceId")
.IsUnique();
b.ToTable("InstanceUsers");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ulong?>("CancelRight")
.HasColumnType("bigint unsigned");
b.Property<ulong?>("CancelRightsType")
.HasColumnType("bigint unsigned");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<long?>("CancelledById")
.HasColumnType("bigint");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<uint?>("ErrorCode")
.HasColumnType("int unsigned");
b.Property<string>("ExceptionDetails")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("datetime(6)");
b.Property<long>("StartedById")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("CancelledById");
b.HasIndex("InstanceId");
b.HasIndex("StartedById");
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ulong>("ExternalUserId")
.HasColumnType("bigint unsigned");
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("int");
b.Property<ushort>("Port")
.HasColumnType("smallint unsigned");
b.Property<int>("ProcessId")
.HasColumnType("int");
b.Property<int>("RebootState")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("AccessToken")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.Property<long>("TestMergeId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("RevisionInformationId");
b.HasIndex("TestMergeId");
b.ToTable("RevInfoTestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Comment")
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("datetime(6)");
b.Property<long>("MergedById")
.HasColumnType("bigint");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("bigint");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("varchar(40) CHARACTER SET utf8mb4")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("MergedById");
b.HasIndex("PrimaryRevisionInformationId")
.IsUnique();
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ulong>("AdministrationRights")
.HasColumnType("bigint unsigned");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("datetime(6)");
b.Property<long?>("CreatedById")
.HasColumnType("bigint");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<ulong>("InstanceManagerRights")
.HasColumnType("bigint unsigned");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("SystemIdentifier")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
.WithOne()
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamDaemonSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamMakerSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
.WithMany()
.HasForeignKey("CancelledById");
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("Jobs")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("RepositorySettings")
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the OAuthConnections table for MYSQL.
/// </summary>
public partial class MYAddOAuthConnections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.CreateTable(
name: "OAuthConnections",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Provider = table.Column<int>(nullable: false),
ExternalUserId = table.Column<ulong>(nullable: false),
UserId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthConnections", x => x.Id);
table.ForeignKey(
name: "FK_OAuthConnections_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_UserId",
table: "OAuthConnections",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_Provider_ExternalUserId",
table: "OAuthConnections",
columns: new[] { "Provider", "ExternalUserId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "OAuthConnections");
}
}
}
@@ -0,0 +1,817 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(PostgresSqlDatabaseContext))]
[Migration("20201122231443_PGAddOAuthConnections")]
partial class PGAddOAuthConnections
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("ChannelLimit")
.HasColumnType("integer");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("boolean");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(100)")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<long>("ReconnectionInterval")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long>("ChatSettingsId")
.HasColumnType("bigint");
b.Property<decimal?>("DiscordChannelId")
.HasColumnType("numeric(20,0)");
b.Property<string>("IrcChannel")
.HasColumnType("character varying(100)")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("boolean");
b.Property<string>("Tag")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("ChatSettingsId", "DiscordChannelId")
.IsUnique();
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique();
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("integer");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("integer");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("integer");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("uuid");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("GitHubDeploymentId")
.HasColumnType("integer");
b.Property<long?>("GitHubRepoId")
.HasColumnType("bigint");
b.Property<long>("JobId")
.HasColumnType("bigint");
b.Property<int?>("MinimumSecurityLevel")
.HasColumnType("integer");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("text");
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId")
.IsUnique();
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("boolean");
b.Property<long>("HeartbeatSeconds")
.HasColumnType("bigint");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int>("SecurityLevel")
.HasColumnType("integer");
b.Property<long>("StartupTimeout")
.HasColumnType("bigint");
b.Property<long>("TopicRequestTimeout")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("ApiValidationPort")
.HasColumnType("integer");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("integer");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("ProjectName")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long>("AutoUpdateInterval")
.HasColumnType("bigint");
b.Property<int>("ChatBotLimit")
.HasColumnType("integer");
b.Property<int>("ConfigurationType")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("boolean");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal>("ByondRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("ChatBotRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("ConfigurationRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("DreamDaemonRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("DreamMakerRights")
.HasColumnType("numeric(20,0)");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<decimal>("InstanceUserRights")
.HasColumnType("numeric(20,0)");
b.Property<decimal>("RepositoryRights")
.HasColumnType("numeric(20,0)");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("UserId", "InstanceId")
.IsUnique();
b.ToTable("InstanceUsers");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal?>("CancelRight")
.HasColumnType("numeric(20,0)");
b.Property<decimal?>("CancelRightsType")
.HasColumnType("numeric(20,0)");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("boolean");
b.Property<long?>("CancelledById")
.HasColumnType("bigint");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<long?>("ErrorCode")
.HasColumnType("bigint");
b.Property<string>("ExceptionDetails")
.HasColumnType("text");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("timestamp with time zone");
b.Property<long>("StartedById")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CancelledById");
b.HasIndex("InstanceId");
b.HasIndex("StartedById");
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal>("ExternalUserId")
.HasColumnType("numeric(20,0)");
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("text");
b.Property<long>("CompileJobId")
.HasColumnType("bigint");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("integer");
b.Property<int>("Port")
.HasColumnType("integer");
b.Property<int>("ProcessId")
.HasColumnType("integer");
b.Property<int>("RebootState")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AccessToken")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("boolean");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
.HasColumnType("boolean");
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("boolean");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<long>("RevisionInformationId")
.HasColumnType("bigint");
b.Property<long>("TestMergeId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("RevisionInformationId");
b.HasIndex("TestMergeId");
b.ToTable("RevInfoTestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("bigint");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Comment")
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("timestamp with time zone");
b.Property<long>("MergedById")
.HasColumnType("bigint");
b.Property<int>("Number")
.HasColumnType("integer");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("bigint");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("character varying(40)")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("MergedById");
b.HasIndex("PrimaryRevisionInformationId")
.IsUnique();
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal>("AdministrationRights")
.HasColumnType("numeric(20,0)");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("timestamp with time zone");
b.Property<long?>("CreatedById")
.HasColumnType("bigint");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("boolean");
b.Property<decimal>("InstanceManagerRights")
.HasColumnType("numeric(20,0)");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(10000)")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("SystemIdentifier")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
.WithOne()
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamDaemonSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamMakerSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
.WithMany()
.HasForeignKey("CancelledById");
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("Jobs")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("RepositorySettings")
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the OAuthConnections table for PostgresSQL.
/// </summary>
public partial class PGAddOAuthConnections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.CreateTable(
name: "OAuthConnections",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Provider = table.Column<int>(nullable: false),
ExternalUserId = table.Column<decimal>(nullable: false),
UserId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthConnections", x => x.Id);
table.ForeignKey(
name: "FK_OAuthConnections_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_UserId",
table: "OAuthConnections",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_Provider_ExternalUserId",
table: "OAuthConnections",
columns: new[] { "Provider", "ExternalUserId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "OAuthConnections");
}
}
}
@@ -0,0 +1,808 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqliteDatabaseContext))]
[Migration("20201122231546_SLAddOAuthConnections")]
partial class SLAddOAuthConnections
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.10");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ushort?>("ChannelLimit")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("ConnectionString")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("Enabled")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(100);
b.Property<int>("Provider")
.HasColumnType("INTEGER");
b.Property<uint?>("ReconnectionInterval")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId", "Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<long>("ChatSettingsId")
.HasColumnType("INTEGER");
b.Property<ulong?>("DiscordChannelId")
.HasColumnType("INTEGER");
b.Property<string>("IrcChannel")
.HasColumnType("TEXT")
.HasMaxLength(100);
b.Property<bool?>("IsAdminChannel")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("IsUpdatesChannel")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("IsWatchdogChannel")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("Tag")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.HasKey("Id");
b.HasIndex("ChatSettingsId", "DiscordChannelId")
.IsUnique();
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique();
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ByondVersion")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("DMApiMajorVersion")
.HasColumnType("INTEGER");
b.Property<int?>("DMApiMinorVersion")
.HasColumnType("INTEGER");
b.Property<int?>("DMApiPatchVersion")
.HasColumnType("INTEGER");
b.Property<Guid?>("DirectoryName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DmeName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("GitHubDeploymentId")
.HasColumnType("INTEGER");
b.Property<long?>("GitHubRepoId")
.HasColumnType("INTEGER");
b.Property<long>("JobId")
.HasColumnType("INTEGER");
b.Property<int?>("MinimumSecurityLevel")
.HasColumnType("INTEGER");
b.Property<string>("Output")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("RevisionInformationId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId")
.IsUnique();
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalParameters")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("AllowWebClient")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("AutoStart")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<uint?>("HeartbeatSeconds")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<int>("SecurityLevel")
.HasColumnType("INTEGER");
b.Property<uint?>("StartupTimeout")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<uint?>("TopicRequestTimeout")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ushort?>("ApiValidationPort")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<int>("ApiValidationSecurityLevel")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("ProjectName")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("RequireDMApiValidation")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint?>("AutoUpdateInterval")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ushort?>("ChatBotLimit")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<int>("ConfigurationType")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("Online")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ByondRights")
.HasColumnType("INTEGER");
b.Property<ulong>("ChatBotRights")
.HasColumnType("INTEGER");
b.Property<ulong>("ConfigurationRights")
.HasColumnType("INTEGER");
b.Property<ulong>("DreamDaemonRights")
.HasColumnType("INTEGER");
b.Property<ulong>("DreamMakerRights")
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<ulong>("InstanceUserRights")
.HasColumnType("INTEGER");
b.Property<ulong>("RepositoryRights")
.HasColumnType("INTEGER");
b.Property<long>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("UserId", "InstanceId")
.IsUnique();
b.ToTable("InstanceUsers");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong?>("CancelRight")
.HasColumnType("INTEGER");
b.Property<ulong?>("CancelRightsType")
.HasColumnType("INTEGER");
b.Property<bool?>("Cancelled")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<long?>("CancelledById")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint?>("ErrorCode")
.HasColumnType("INTEGER");
b.Property<string>("ExceptionDetails")
.HasColumnType("TEXT");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("StartedById")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("StoppedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CancelledById");
b.HasIndex("InstanceId");
b.HasIndex("StartedById");
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ExternalUserId")
.HasColumnType("INTEGER");
b.Property<int>("Provider")
.HasColumnType("INTEGER");
b.Property<long?>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessIdentifier")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("CompileJobId")
.HasColumnType("INTEGER");
b.Property<int>("LaunchSecurityLevel")
.HasColumnType("INTEGER");
b.Property<ushort>("Port")
.HasColumnType("INTEGER");
b.Property<int>("ProcessId")
.HasColumnType("INTEGER");
b.Property<int>("RebootState")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<string>("AccessUser")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("CommitterEmail")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<string>("CommitterName")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<bool?>("CreateGitHubDeployments")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<bool?>("PostTestMergeComment")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("PushTestMergeCommits")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<long>("RevisionInformationId")
.HasColumnType("INTEGER");
b.Property<long>("TestMergeId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RevisionInformationId");
b.HasIndex("TestMergeId");
b.ToTable("RevInfoTestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
b.Property<long>("InstanceId")
.HasColumnType("INTEGER");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("InstanceId", "CommitSha")
.IsUnique();
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("BodyAtMerge")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Comment")
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<DateTimeOffset>("MergedAt")
.HasColumnType("TEXT");
b.Property<long>("MergedById")
.HasColumnType("INTEGER");
b.Property<int>("Number")
.HasColumnType("INTEGER");
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<string>("PullRequestRevision")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(40);
b.Property<string>("TitleAtMerge")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("MergedById");
b.HasIndex("PrimaryRevisionInformationId")
.IsUnique();
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long?>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("AdministrationRights")
.HasColumnType("INTEGER");
b.Property<string>("CanonicalName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long?>("CreatedById")
.HasColumnType("INTEGER");
b.Property<bool?>("Enabled")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ulong>("InstanceManagerRights")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LastPasswordUpdate")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(10000);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("SystemIdentifier")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.HasIndex("SystemIdentifier")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
.WithOne()
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamDaemonSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("DreamMakerSettings")
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", null)
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
.WithMany()
.HasForeignKey("CancelledById");
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("Jobs")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithOne("RepositorySettings")
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.ClientNoAction)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Adds the OAuthConnections table for SQLite.
/// </summary>
public partial class SLAddOAuthConnections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.CreateTable(
name: "OAuthConnections",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Provider = table.Column<int>(nullable: false),
ExternalUserId = table.Column<ulong>(nullable: false),
UserId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthConnections", x => x.Id);
table.ForeignKey(
name: "FK_OAuthConnections_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_UserId",
table: "OAuthConnections",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthConnections_Provider_ExternalUserId",
table: "OAuthConnections",
columns: new[] { "Provider", "ExternalUserId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
if (migrationBuilder == null)
throw new ArgumentNullException(nameof(migrationBuilder));
migrationBuilder.DropTable(
name: "OAuthConnections");
}
}
}
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.7")
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -373,6 +373,31 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<ulong>("ExternalUserId")
.HasColumnType("bigint unsigned");
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
@@ -705,6 +730,14 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Database.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.7")
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -374,6 +374,32 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<decimal>("ExternalUserId")
.HasColumnType("numeric(20,0)");
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
@@ -712,6 +738,14 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.7")
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
@@ -376,6 +376,32 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ExternalUserId")
.HasColumnType("decimal(20,0)");
b.Property<int>("Provider")
.HasColumnType("int");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
@@ -715,6 +741,14 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.7");
.HasAnnotation("ProductVersion", "3.1.10");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
@@ -372,6 +372,31 @@ namespace Tgstation.Server.Host.Database.Migrations
b.ToTable("Jobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ExternalUserId")
.HasColumnType("INTEGER");
b.Property<int>("Provider")
.HasColumnType("INTEGER");
b.Property<long?>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("Provider", "ExternalUserId")
.IsUnique();
b.ToTable("OAuthConnections");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.Property<long>("Id")
@@ -704,6 +729,14 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired();
});
modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "User")
.WithMany("OAuthConnections")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
@@ -0,0 +1,26 @@
namespace Tgstation.Server.Host.Models
{
/// <inheritdoc />
public sealed class OAuthConnection : Api.Models.OAuthConnection
{
/// <summary>
/// The row Id.
/// </summary>
public long Id { get; set; }
/// <summary>
/// The owning <see cref="Models.User"/>.
/// </summary>
public User User { get; set; }
/// <summary>
/// Convert the <see cref="OAuthConnection"/> to it's API form.
/// </summary>
/// <returns>A new <see cref="Api.Models.OAuthConnection"/>.</returns>
public Api.Models.OAuthConnection ToApi() => new Api.Models.OAuthConnection
{
Provider = Provider,
ExternalUserId = ExternalUserId
};
}
}
+6 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -48,6 +48,11 @@ namespace Tgstation.Server.Host.Models
/// </summary>
public ICollection<TestMerge> TestMerges { get; set; }
/// <summary>
/// The <see cref="TestMerge"/>s made by the <see cref="User"/>
/// </summary>
public ICollection<OAuthConnection> OAuthConnections { get; set; }
/// <summary>
/// Change a <see cref="Api.Models.Internal.User.Name"/> into a <see cref="CanonicalName"/>.
/// </summary>
@@ -1,4 +1,4 @@
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Tokens;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
@@ -19,8 +19,9 @@ namespace Tgstation.Server.Host.Security
/// Create a <see cref="Token"/> for a given <paramref name="user"/>
/// </summary>
/// <param name="user">The <see cref="Models.User"/> to create the token for. Must have the <see cref="Api.Models.Internal.User.Id"/> field available</param>
/// <param name="oAuth">Whether or not this is an OAuth login.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="Token"/></returns>
Task<Token> CreateToken(Models.User user, CancellationToken cancellationToken);
Task<Token> CreateToken(Models.User user, bool oAuth, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,95 @@
using Microsoft.Extensions.Logging;
using Octokit;
using System;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
/// <summary>
/// <see cref="IOAuthValidator"/> for GitHub.
/// </summary>
sealed class GitHubOAuthValidator : IOAuthValidator
{
/// <inheritdoc />
public OAuthProvider Provider => OAuthProvider.GitHub;
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubOAuthValidator"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="GitHubOAuthValidator"/>.
/// </summary>
readonly ILogger<GitHubOAuthValidator> logger;
/// <summary>
/// The <see cref="GitHubOAuthConfiguration"/> for the <see cref="GitHubOAuthValidator"/>.
/// </summary>
readonly GitHubOAuthConfiguration gitHubOAuthConfiguration;
/// <summary>
/// Initializes a new instance of the <see cref="GitHubOAuthValidator"/> <see langword="class"/>.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="gitHubOAuthConfiguration">The value of <see cref="gitHubOAuthConfiguration"/>.</param>
public GitHubOAuthValidator(
IGitHubClientFactory gitHubClientFactory,
ILogger<GitHubOAuthValidator> logger,
GitHubOAuthConfiguration gitHubOAuthConfiguration)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.gitHubOAuthConfiguration = gitHubOAuthConfiguration ?? throw new ArgumentNullException(nameof(gitHubOAuthConfiguration));
}
/// <inheritdoc />
public async Task<ulong?> ValidateResponseCode(string code, CancellationToken cancellationToken)
{
if (code == null)
throw new ArgumentNullException(nameof(code));
var client = gitHubClientFactory.CreateClient();
try
{
logger.LogTrace("Validating response code...");
var response = await client
.Oauth
.CreateAccessToken(
new OauthTokenRequest(
gitHubOAuthConfiguration.ClientId,
gitHubOAuthConfiguration.ClientSecret,
code))
.ConfigureAwait(false);
var token = response.AccessToken;
if (token == null)
return null;
var authenticatedClient = gitHubClientFactory.CreateClient(token);
logger.LogTrace("Getting user details...");
var userDetails = await authenticatedClient
.User
.Current()
.ConfigureAwait(false);
return (ulong)userDetails.Id;
}
catch (RateLimitExceededException)
{
throw;
}
catch(ApiException ex)
{
logger.LogWarning(ex, "API error while completing OAuth handshake!");
return null;
}
}
}
}
@@ -0,0 +1,17 @@
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Security.OAuth
{
/// <summary>
/// Contains <see cref="IOAuthValidator"/>s
/// </summary>
public interface IOAuthProviders
{
/// <summary>
/// Gets the <see cref="IOAuthValidator"/> for a given <paramref name="oAuthProvider"/>.
/// </summary>
/// <param name="oAuthProvider">The <see cref="OAuthProvider"/> to get the validator for.</param>
/// <returns>The <see cref="IOAuthValidator"/> for <paramref name="oAuthProvider"/>.</returns>
IOAuthValidator GetValidator(OAuthProvider oAuthProvider);
}
}
@@ -0,0 +1,25 @@
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Security.OAuth
{
/// <summary>
/// Validates OAuth responses for a given <see cref="Provider"/>.
/// </summary>
public interface IOAuthValidator
{
/// <summary>
/// The <see cref="OAuthProvider"/> this validator is for.
/// </summary>
OAuthProvider Provider { get; }
/// <summary>
/// Validate a given OAuth response <paramref name="code"/>.
/// </summary>
/// <param name="code">The OAuth response string from web application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="null"/> if authentication failed, <see cref="global::System.UInt64.MaxValue"/> if a rate limit occurred, and the validated <see cref="OAuthConnection.ExternalUserId"/> otherwise.</returns>
Task<ulong?> ValidateResponseCode(string code, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,51 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security.OAuth
{
/// <inheritdoc />
sealed class OAuthProviders : IOAuthProviders
{
/// <summary>
/// The <see cref="IReadOnlyCollection{T}"/> of <see cref="IOAuthValidator"/>s.
/// </summary>
readonly IReadOnlyCollection<IOAuthValidator> validators;
/// <summary>
/// Initializes a new instance of the <see cref="OAuthProviders"/> <see langword="class"/>.
/// </summary>
/// <param name="gitHubClientFactory">The <see cref="IGitHubClientFactory"/> to use.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="SecurityConfiguration"/> to use.</param>
public OAuthProviders(
IGitHubClientFactory gitHubClientFactory,
ILoggerFactory loggerFactory,
IOptions<SecurityConfiguration> securityConfigurationOptions)
{
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
var securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions));
var validatorsBuilder = new List<IOAuthValidator>();
if (securityConfiguration.GitHubOAuth != null)
validatorsBuilder.Add(
new GitHubOAuthValidator(
gitHubClientFactory,
loggerFactory.CreateLogger<GitHubOAuthValidator>(),
securityConfiguration.GitHubOAuth));
validators = validatorsBuilder;
}
/// <inheritdoc />
public IOAuthValidator GetValidator(OAuthProvider oAuthProvider) => validators.First(x => x.Provider == oAuthProvider);
}
}
@@ -76,7 +76,7 @@ namespace Tgstation.Server.Host.Security
}
/// <inheritdoc />
public async Task<Token> CreateToken(Models.User user, CancellationToken cancellationToken)
public async Task<Token> CreateToken(Models.User user, bool oAuth, CancellationToken cancellationToken)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
@@ -93,7 +93,9 @@ namespace Tgstation.Server.Host.Security
if (nowUnix == lpuUnix)
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
var expiry = now.AddMinutes(securityConfiguration.TokenExpiryMinutes);
var expiry = now.AddMinutes(oAuth
? securityConfiguration.OAuthTokenExpiryMinutes
: securityConfiguration.TokenExpiryMinutes);
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.Value.ToString(CultureInfo.InvariantCulture)),
@@ -1,4 +1,4 @@
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Setup
@@ -18,6 +18,11 @@ namespace Tgstation.Server.Host.Setup
/// </summary>
DatabaseConfiguration DatabaseConfiguration { get; }
/// <summary>
/// The <see cref="Configuration.SecurityConfiguration"/>.
/// </summary>
SecurityConfiguration SecurityConfiguration { get; }
/// <summary>
/// The <see cref="Configuration.FileLoggingConfiguration"/>.
/// </summary>
@@ -18,6 +18,9 @@ namespace Tgstation.Server.Host.Setup
/// <inheritdoc />
public DatabaseConfiguration DatabaseConfiguration => databaseConfigurationOptions.Value;
/// <inheritdoc />
public SecurityConfiguration SecurityConfiguration => securityConfigurationOptions.Value;
/// <inheritdoc />
public FileLoggingConfiguration FileLoggingConfiguration => fileLoggingConfigurationOptions.Value;
@@ -34,6 +37,11 @@ namespace Tgstation.Server.Host.Setup
/// </summary>
readonly IOptions<DatabaseConfiguration> databaseConfigurationOptions;
/// <summary>
/// Backing <see cref="IOptions{TOptions}"/> for <see cref="SecurityConfiguration"/>.
/// </summary>
readonly IOptions<SecurityConfiguration> securityConfigurationOptions;
/// <summary>
/// Backing <see cref="IOptions{TOptions}"/> for <see cref="FileLoggingConfiguration"/>.
/// </summary>
@@ -46,12 +54,14 @@ namespace Tgstation.Server.Host.Setup
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used to create <see cref="Logger"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="GeneralConfiguration"/>.</param>
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="DatabaseConfiguration"/>.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="SecurityConfiguration"/>.</param>
/// <param name="fileLoggingConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="FileLoggingConfiguration"/>.</param>
public PostSetupServices(
IPlatformIdentifier platformIdentifier,
ILoggerFactory loggerFactory,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<DatabaseConfiguration> databaseConfigurationOptions,
IOptions<SecurityConfiguration> securityConfigurationOptions,
IOptions<FileLoggingConfiguration> fileLoggingConfigurationOptions)
{
PlatformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
@@ -61,6 +71,7 @@ namespace Tgstation.Server.Host.Setup
Logger = loggerFactory.CreateLogger<TLoggerType>();
this.generalConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
this.databaseConfigurationOptions = databaseConfigurationOptions ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
this.securityConfigurationOptions = securityConfigurationOptions ?? throw new ArgumentNullException(nameof(securityConfigurationOptions));
this.fileLoggingConfigurationOptions = fileLoggingConfigurationOptions ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
}
}
@@ -62,6 +62,7 @@ namespace Tgstation.Server.Host.Setup
services.UseStandardConfig<GeneralConfiguration>(Configuration);
services.UseStandardConfig<DatabaseConfiguration>(Configuration);
services.UseStandardConfig<SecurityConfiguration>(Configuration);
services.UseStandardConfig<FileLoggingConfiguration>(Configuration);
ConfigureHostedService(services);
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../../build/Version.props" />
<PropertyGroup>
@@ -66,20 +66,20 @@
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.6" />
<PackageReference Include="Discord.Net.WebSocket" Version="2.2.0" />
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0034" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.7" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.10" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.10" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -87,7 +87,7 @@
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
<PackageReference Include="Octokit" Version="0.48.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.2.4" />
<!-- If this is updated, be sure to update the reference in the README.md -->
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
@@ -97,13 +97,13 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.6.3" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="4.7.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.1" />
<PackageReference Include="System.Management" Version="4.7.0" />
<PackageReference Include="Wangkanai.Detection.Browser" Version="2.0.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="5.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
<PackageReference Include="System.Management" Version="5.0.0" />
<PackageReference Include="Wangkanai.Detection.Browser" Version="2.0.1" />
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="3.0.57" />
</ItemGroup>
+2 -1
View File
@@ -54,6 +54,7 @@
"TokenExpiryMinutes": 15,
"TokenClockSkewMinutes": 1,
"TokenSigningKeyByteAmount": 256,
"CustomTokenSigningKeyBase64": null
"CustomTokenSigningKeyBase64": null,
"GitHubOAuth": null
}
}
@@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net.Http.Headers;
using System.Net.Mime;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Api.Tests
{
@@ -21,6 +22,7 @@ namespace Tgstation.Server.Api.Tests
Assert.ThrowsException<ArgumentNullException>(() => new ApiHeaders(null, null));
Assert.ThrowsException<ArgumentNullException>(() => new ApiHeaders(productHeaderValue, null));
var headers = new ApiHeaders(productHeaderValue, String.Empty);
headers = new ApiHeaders(productHeaderValue, String.Empty, OAuthProvider.GitHub);
}
[TestMethod]