Merge pull request #1161 from tgstation/1160-KeyCloak

Adds Keycloak OAuth
This commit is contained in:
Jordan Brown
2020-12-07 17:19:20 -05:00
committed by GitHub
29 changed files with 238 additions and 116 deletions
+2 -2
View File
@@ -203,7 +203,7 @@ jobs:
- name: Set TGS4_GITHUB_REF for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "TGS4_GITHUB_REF=${{ github.event.base_ref }}" >> $env:GITHUB_ENV
run: echo "TGS4_GITHUB_REF=${{ github.base_ref }}" >> $env:GITHUB_ENV
- name: Set TGS4_GITHUB_REF for push
if: ${{ github.event_name == 'push' }}
@@ -345,7 +345,7 @@ jobs:
- name: Set TGS4_GITHUB_REF for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "TGS4_GITHUB_REF=${{ github.event.base_ref }}" >> $GITHUB_ENV
run: echo "TGS4_GITHUB_REF=${{ github.base_ref }}" >> $GITHUB_ENV
- name: Set TGS4_GITHUB_REF for push
if: ${{ github.event_name == 'push' }}
+13 -2
View File
@@ -133,10 +133,21 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi
- `Security:<Provider Name>OAuth`: Sets the OAuth client ID and secret for a given `<Provider Name>`. The currently supported providers are `GitHub`, `Discord`, and `TGForums`. Setting these fields to `null` disables logins with the provider, but does not stop users from associating their accounts using the API. Sample Entry:
```json
"GitHubOAuth":{
"ClientId": "... (Note for `TGForums`, this is the redirect_uri used)",
"ClientSecret": "..."
"ClientId": "...",
"ClientSecret": "...",
"RedirectUrl": "...", (Used with certain providers)
"ServerUrl": "...", (Used with certain providers)
}
```
The following providers use the `RedirectUrl` setting:
- GitHub
- TGForums
- Keycloak
The following providers use the `ServerUrl` setting:
- Keycloak
### Database Configuration
+8 -7
View File
@@ -63,7 +63,7 @@ TGS will only every return the response codes listed here
- 204: No Content. Identical to 200 with no response body.
- 400: Bad Request. The response body will contain an @ref Tgstation.Server.Api.Models.ErrorMessage model detailing the error
- 401: Unauthorized. Invalid or expired credentials were provided. Check rights APIs for updates. See @ref api_auth for details
- 403: Forbidden. User tried to make a request they were not allowed to perform.
- 403: Forbidden. User tried to make a request they were not allowed to perform.
- 404: Not found. A resource was requested that had never existed. In the case of retrieving a resource by ID, it could potentially exist in the future
- 406: Not Acceptable. Consequence of failing to provide an Accept header
- 408: Request Timeout. The client took to long to continue a request
@@ -97,7 +97,7 @@ Other fields may be present in the Version model but should be ignored. See a de
@section api_auth Authentication
Every request made to TGS requires authentication. It is provided in the form of the Authorization header.
Every request made to TGS requires authentication. It is provided in the form of the Authorization header.
The first request made to TGS must be to login the user
@@ -128,7 +128,7 @@ TGS4 supports OAuth 2.0 with select providers for authentication.
The flow for this is as follows:
- Retrieve the @ref api_ver to find out available OAuth providers and their respective client IDs.
- Retrieve the @ref api_ver to find out available OAuth providers and their respective client ID and redirect URIs.
- Send the user to the Authorization Request endpoint for the provider using the client ID from above. See https://tools.ietf.org/html/rfc6749#section-4.1.1. DO NOT specify a redirect URI, this should be configured in the provider.
- Retrieve the authorization response code after successfully completing the authorize step above.
- Perform the following request:
@@ -144,9 +144,10 @@ You will be granted a bearer token as in basic auth. This will have an extended
@subsubsection api_auth_o_providers Supported Providers
- ID: 0, Name: GitHub, Documentation: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/
- ID: 1, Name: Discord, Documentation: https://discord.com/developers/docs/topics/oauth2
- ID: 2, Name: TGForums, Documentation: https://tgstation13.org/phpBB/viewtopic.php?f=45&t=9922
- GitHub: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps
- Discord: https://discord.com/developers/docs/topics/oauth2
- TGForums: https://tgstation13.org/phpBB/viewtopic.php?f=45&t=9922
- Keycloak: https://plugins.miniorange.com/keycloak-single-sign-on-wordpress-sso-oauth-openid-connect
@section api_perms Permissions
@@ -391,7 +392,7 @@ If the server detects a set of @ref Tgstation.Server.Api.Models.TestMergeParamet
@subsubsection api_repounsetauth Unsetting Authentication
The repository uses the @ref Tgstation.Server.Api.Models.Repository.AccessUser and @ref Tgstation.Server.Api.Models.Repository.AccessToken credentials to access the remote repository if these fields are set. To unset them you must set both of them to an empty string like so
The repository uses the @ref Tgstation.Server.Api.Models.Repository.AccessUser and @ref Tgstation.Server.Api.Models.Repository.AccessToken credentials to access the remote repository if these fields are set. To unset them you must set both of them to an empty string like so
@code{.json}
{
@@ -1,3 +1,5 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Api.Models
@@ -10,6 +12,7 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// The <see cref="OAuthProvider"/> of the <see cref="OAuthConnection"/>.
/// </summary>]
[JsonConverter(typeof(StringEnumConverter))]
[EnumDataType(typeof(OAuthProvider))]
public OAuthProvider Provider { get; set; }
@@ -19,5 +19,10 @@ namespace Tgstation.Server.Api.Models
/// https://tgstation13.org
/// </summary>
TGForums,
/// <summary>
/// https://www.keycloak.org
/// </summary>
Keycloak,
}
}
@@ -0,0 +1,20 @@
using System;
namespace Tgstation.Server.Api.Models
{
/// <summary>
/// Public information about a given <see cref="OAuthProvider"/>.
/// </summary>
public sealed class OAuthProviderInfo
{
/// <summary>
/// The client ID.
/// </summary>
public string? ClientId { get; set; }
/// <summary>
/// The redirect URL.
/// </summary>
public Uri? RedirectUri { get; set; }
}
}
@@ -24,8 +24,8 @@ namespace Tgstation.Server.Api.Models
public Version? DMApiVersion { get; set; }
/// <summary>
/// Map of <see cref="OAuthProvider"/> to the server's associated client IDs for them.
/// Map of <see cref="OAuthProvider"/> to the <see cref="OAuthProviderInfo"/> for them.
/// </summary>
public IDictionary<OAuthProvider, string>? OAuthProviderClientIds { get; set; }
public IDictionary<OAuthProvider, OAuthProviderInfo>? OAuthProviderInfos { get; set; }
}
}
@@ -37,6 +37,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
@@ -31,7 +31,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
@@ -3,35 +3,18 @@ using System;
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
/// OAuth options.
/// OAuth configuration options.
/// </summary>
class OAuthConfiguration
sealed class OAuthConfiguration : OAuthConfigurationBase
{
/// <summary>
/// The client ID.
/// The client redirect URL. Not used by all providers.
/// </summary>
public string ClientId { get; set; }
public Uri ServerUrl { get; set; }
/// <summary>
/// The client secret.
/// The authentication server URL. Not used by all providers.
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthConfiguration"/> <see langword="class"/>.
/// </summary>
public OAuthConfiguration() { }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthConfiguration"/> <see langword="class"/>.
/// </summary>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> to copy settings from.</param>
public OAuthConfiguration(OAuthConfiguration oAuthConfiguration)
{
if (oAuthConfiguration == null)
throw new ArgumentNullException(nameof(oAuthConfiguration));
ClientId = oAuthConfiguration.ClientId;
ClientSecret = oAuthConfiguration.ClientSecret;
}
public Uri RedirectUrl { get; set; }
}
}
@@ -0,0 +1,37 @@
using System;
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
/// Base OAuth options.
/// </summary>
abstract class OAuthConfigurationBase
{
/// <summary>
/// The client ID.
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// The client secret.
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthConfigurationBase"/> <see langword="class"/>.
/// </summary>
public OAuthConfigurationBase() { }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthConfigurationBase"/> <see langword="class"/>.
/// </summary>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfigurationBase"/> to copy settings from.</param>
public OAuthConfigurationBase(OAuthConfigurationBase oAuthConfiguration)
{
if (oAuthConfiguration == null)
throw new ArgumentNullException(nameof(oAuthConfiguration));
ClientId = oAuthConfiguration.ClientId;
ClientSecret = oAuthConfiguration.ClientSecret;
}
}
}
@@ -167,7 +167,7 @@ namespace Tgstation.Server.Host.Controllers
InstanceLimit = generalConfiguration.InstanceLimit,
UserLimit = generalConfiguration.UserLimit,
ValidInstancePaths = generalConfiguration.ValidInstancePaths,
OAuthProviderClientIds = await oAuthProviders.ClientIds(cancellationToken).ConfigureAwait(false)
OAuthProviderInfos = await oAuthProviders.ProviderInfos(cancellationToken).ConfigureAwait(false)
});
}
+3 -1
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Tgstation.Server.Host.Models
{
@@ -75,7 +76,8 @@ namespace Tgstation.Server.Host.Models
Id = Id,
InstanceManagerRights = showDetails ? InstanceManagerRights : null,
Name = Name,
SystemIdentifier = showDetails ? SystemIdentifier : null
SystemIdentifier = showDetails ? SystemIdentifier : null,
OAuthConnections = OAuthConnections?.Select(x => x.ToApi()).ToList(),
};
/// <summary>
@@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
@@ -60,6 +60,7 @@ namespace Tgstation.Server.Host.Security
.AsQueryable()
.Where(x => x.Id == userId)
.Include(x => x.CreatedBy)
.Include(x => x.OAuthConnections)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
if (user == default)
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Security.OAuth
}
/// <inheritdoc />
public abstract Task<string> GetClientId(CancellationToken cancellationToken);
public abstract Task<OAuthProviderInfo> GetProviderInfo(CancellationToken cancellationToken);
/// <inheritdoc />
public abstract Task<string> ValidateResponseCode(string code, CancellationToken cancellationToken);
@@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.Security.OAuth
protected override Uri UserInformationUrl => new Uri("https://discord.com/api/users/@me");
/// <inheritdoc />
protected override OAuthTokenRequest CreateTokenRequest(string code) => new DiscordTokenRequest(OAuthConfiguration, code);
protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "identify");
/// <inheritdoc />
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
@@ -1,33 +0,0 @@
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Security.OAuth
{
/// <summary>
/// <see cref="OAuthTokenRequest"/> for Discord.
/// </summary>
/// <remarks>See https://discord.com/developers/docs/topics/oauth2</remarks>
sealed class DiscordTokenRequest : OAuthTokenRequest
{
/// <summary>
/// The 'grant_type' field.
/// </summary>
public string GrantType { get; }
/// <summary>
/// The 'scope' field.
/// </summary>
public string Scope { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordTokenRequest"/> <see langword="class"/>.
/// </summary>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> for the <see cref="OAuthTokenRequest"/>.</param>
/// <param name="code">The OAuth code for the <see cref="OAuthTokenRequest"/>.</param>
public DiscordTokenRequest(OAuthConfiguration oAuthConfiguration, string code)
: base(oAuthConfiguration, code)
{
GrantType = "authorization_code";
Scope = "identify";
}
}
}
@@ -8,6 +8,7 @@ using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
@@ -95,7 +96,10 @@ namespace Tgstation.Server.Host.Security.OAuth
var accessToken = DecodeTokenPayload(tokenResponseJson);
if (accessToken == null)
{
Logger.LogTrace("No token from DecodeTokenPayload!");
return null;
}
Logger.LogTrace("Getting user details...");
using var userInformationRequest = new HttpRequestMessage(HttpMethod.Get, UserInformationUrl);
@@ -119,6 +123,11 @@ namespace Tgstation.Server.Host.Security.OAuth
}
/// <inheritdoc />
public override Task<string> GetClientId(CancellationToken cancellationToken) => Task.FromResult(OAuthConfiguration.ClientId);
public override Task<OAuthProviderInfo> GetProviderInfo(CancellationToken cancellationToken) => Task.FromResult(
new OAuthProviderInfo
{
ClientId = OAuthConfiguration.ClientId,
RedirectUri = OAuthConfiguration.RedirectUrl
});
}
}
@@ -65,7 +65,10 @@ namespace Tgstation.Server.Host.Security.OAuth
new OauthTokenRequest(
oAuthConfiguration.ClientId,
oAuthConfiguration.ClientSecret,
code))
code)
{
RedirectUri = oAuthConfiguration.RedirectUrl
})
.ConfigureAwait(false);
var token = response.AccessToken;
@@ -94,6 +97,11 @@ namespace Tgstation.Server.Host.Security.OAuth
}
/// <inheritdoc />
public Task<string> GetClientId(CancellationToken cancellationToken) => Task.FromResult(oAuthConfiguration.ClientId);
public Task<OAuthProviderInfo> GetProviderInfo(CancellationToken cancellationToken) => Task.FromResult(
new OAuthProviderInfo
{
ClientId = oAuthConfiguration.ClientId,
RedirectUri = oAuthConfiguration.RedirectUrl
});
}
}
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Security.OAuth
/// Gets a <see cref="Dictionary{TKey, TValue}"/> of the provider client IDs.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a anew <see cref="Dictionary{TKey, TValue}"/> of the active provider client IDs.</returns>
Task<Dictionary<OAuthProvider, string>> ClientIds(CancellationToken cancellationToken);
/// <returns>A <see cref="Task{TResult}"/> resulting in a anew <see cref="Dictionary{TKey, TValue}"/> of the active <see cref="OAuthProviderInfo"/>s.</returns>
Task<Dictionary<OAuthProvider, OAuthProviderInfo>> ProviderInfos(CancellationToken cancellationToken);
}
}
@@ -15,11 +15,11 @@ namespace Tgstation.Server.Host.Security.OAuth
OAuthProvider Provider { get; }
/// <summary>
/// Gets the OAuth client ID of validator.
/// Gets the <see cref="OAuthProvider"/> of validator.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the client ID of the validator on success, <see langword="null"/> on failure.</returns>
Task<string> GetClientId(CancellationToken cancellationToken);
Task<OAuthProviderInfo> GetProviderInfo(CancellationToken cancellationToken);
/// <summary>
/// Validate a given OAuth response <paramref name="code"/>.
@@ -0,0 +1,54 @@
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Security.OAuth
{
/// <summary>
/// OAuth validator for Keycloak.
/// </summary>
sealed class KeycloakOAuthValidator : GenericOAuthValidator
{
/// <inheritdoc />
public override OAuthProvider Provider => OAuthProvider.Keycloak;
/// <inheritdoc />
protected override Uri TokenUrl => new Uri($"{BaseProtocolPath}/token");
/// <inheritdoc />
protected override Uri UserInformationUrl => new Uri($"{BaseProtocolPath}/userinfo");
/// <summary>
/// Base path to the server's OAuth endpoint.
/// </summary>
string BaseProtocolPath => $"{OAuthConfiguration.ServerUrl}/protocol/openid-connect";
/// <summary>
/// Initializes a new instance of the <see cref="KeycloakOAuthValidator"/> <see langword="class"/>.
/// </summary>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GenericOAuthValidator"/>.</param>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> for the <see cref="GenericOAuthValidator"/>.</param>
public KeycloakOAuthValidator(
IHttpClientFactory httpClientFactory,
IAssemblyInformationProvider assemblyInformationProvider,
ILogger<KeycloakOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
: base(httpClientFactory, assemblyInformationProvider, logger, oAuthConfiguration)
{
}
/// <inheritdoc />
protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "openid");
/// <inheritdoc />
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
/// <inheritdoc />
protected override string DecodeUserInformationPayload(dynamic responseJson) => responseJson.sub;
}
}
@@ -58,7 +58,7 @@ namespace Tgstation.Server.Host.Security.OAuth
loggerFactory.CreateLogger<DiscordOAuthValidator>(),
discordConfig));
if(securityConfiguration.OAuth.TryGetValue(OAuthProvider.TGForums, out var tgConfig))
if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.TGForums, out var tgConfig))
validatorsBuilder.Add(
new TGForumsOAuthValidator(
httpClientFactory,
@@ -66,6 +66,14 @@ namespace Tgstation.Server.Host.Security.OAuth
loggerFactory.CreateLogger<TGForumsOAuthValidator>(),
tgConfig));
if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.Keycloak, out var keyCloakConfig))
validatorsBuilder.Add(
new KeycloakOAuthValidator(
httpClientFactory,
assemblyInformationProvider,
loggerFactory.CreateLogger<KeycloakOAuthValidator>(),
keyCloakConfig));
validators = validatorsBuilder;
}
@@ -73,11 +81,11 @@ namespace Tgstation.Server.Host.Security.OAuth
public IOAuthValidator GetValidator(OAuthProvider oAuthProvider) => validators.FirstOrDefault(x => x.Provider == oAuthProvider);
/// <inheritdoc />
public async Task<Dictionary<OAuthProvider, string>> ClientIds(CancellationToken cancellationToken)
public async Task<Dictionary<OAuthProvider, OAuthProviderInfo>> ProviderInfos(CancellationToken cancellationToken)
{
var providersAndTasks = validators.ToDictionary(
x => x.Provider,
x => x.GetClientId(cancellationToken));
x => x.GetProviderInfo(cancellationToken));
await Task.WhenAll(providersAndTasks.Values).ConfigureAwait(false);
@@ -6,22 +6,42 @@ namespace Tgstation.Server.Host.Security.OAuth
/// <summary>
/// Generic OAuth token request.
/// </summary>
class OAuthTokenRequest : OAuthConfiguration
sealed class OAuthTokenRequest : OAuthConfigurationBase
{
/// <summary>
/// The OAuth code.
/// The OAuth code received from the browser.
/// </summary>
public string Code { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthTokenRequest"/>
/// The scopes being requested.
/// </summary>
public string Scope { get; }
/// <summary>
/// The OAuth redirect URI.
/// </summary>
public Uri RedirectUri { get; }
/// <summary>
/// The OAuth grant type.
/// </summary>
public string GrantType { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthTokenRequest"/> <see langword="class"/>.
/// </summary>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/> to build from.</param>
/// <param name="code">The OAuth code received from the browser.</param>
public OAuthTokenRequest(OAuthConfiguration oAuthConfiguration, string code)
/// <param name="code">The value of <see cref="Code"/>.</param>
/// <param name="scope">The value of <see cref="Scope"/></param>
public OAuthTokenRequest(OAuthConfiguration oAuthConfiguration, string code, string scope)
: base(oAuthConfiguration)
{
Code = code ?? throw new ArgumentNullException(nameof(code));
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
RedirectUri = oAuthConfiguration.RedirectUrl;
GrantType = "authorization_code";
}
}
}
@@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Security.OAuth
}
/// <inheritdoc />
public override async Task<string> GetClientId(CancellationToken cancellationToken)
public override async Task<OAuthProviderInfo> GetProviderInfo(CancellationToken cancellationToken)
{
var expiredSessions = sessions.RemoveAll(x => x.Item2.AddMinutes(SessionRetentionMinutes) < DateTimeOffset.Now);
if (expiredSessions > 0)
@@ -65,7 +65,7 @@ namespace Tgstation.Server.Host.Security.OAuth
{
UriBuilder builder = new UriBuilder("https://tgstation13.org/phpBB/oauth_create_session.php")
{
Query = $"site_private_token={HttpUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(OAuthConfiguration.ClientSecret)))}&return_uri={HttpUtility.UrlEncode(OAuthConfiguration.ClientId)}"
Query = $"site_private_token={HttpUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(OAuthConfiguration.ClientSecret)))}&return_uri={HttpUtility.UrlEncode(OAuthConfiguration.RedirectUrl.ToString())}"
};
using var request = new HttpRequestMessage(HttpMethod.Get, builder.Uri);
@@ -83,8 +83,15 @@ namespace Tgstation.Server.Host.Security.OAuth
return null;
}
sessions.Add(Tuple.Create(newSession, DateTimeOffset.Now));
return newSession.SessionPublicToken;
sessions.Add(
Tuple.Create(
newSession,
DateTimeOffset.Now));
return new OAuthProviderInfo
{
ClientId = newSession.SessionPublicToken,
RedirectUri = OAuthConfiguration.RedirectUrl
};
}
catch (Exception ex)
{
+2 -1
View File
@@ -58,7 +58,8 @@
"OAuth": {
"GitHub": null,
"Discord": null,
"TGForums": null
"TGForums": null,
"Keycloak": null
}
}
}
@@ -22,7 +22,6 @@ namespace Tgstation.Server.Tests.Instance
public async Task Run(Task repositoryTask, CancellationToken cancellationToken)
{
Assert.IsFalse(repositoryTask.IsCompleted);
var deployJob = await dreamMakerClient.Compile(cancellationToken);
deployJob = await WaitForJob(deployJob, 30, true, null, cancellationToken);
Assert.IsTrue(deployJob.ErrorCode == ErrorCode.RepoCloning || deployJob.ErrorCode == ErrorCode.RepoMissing);
@@ -22,28 +22,13 @@ namespace Tgstation.Server.Tests.Instance
public async Task RunPreWatchdog(CancellationToken cancellationToken)
{
const string GitHubRef = "TGS4_GITHUB_REF";
var branchSourceEnvVars = new List<string>
{
"TGS4_TEST_BRANCH",
"APPVEYOR_REPO_BRANCH",
"TRAVIS_BRANCH",
GitHubRef
};
const string TestRefEnvVar = "TGS4_GITHUB_REF";
var envVar = Environment.GetEnvironmentVariable(TestRefEnvVar);
string workingBranch = null;
foreach (var envVarName in branchSourceEnvVars)
if (!String.IsNullOrWhiteSpace(envVar))
{
var envVar = Environment.GetEnvironmentVariable(envVarName);
if (!String.IsNullOrWhiteSpace(envVar))
{
if(envVarName == GitHubRef)
envVar = envVar.Substring("refs/heads/".Length);
workingBranch = envVar;
Console.WriteLine($"TEST: Set working branch to '{workingBranch}' from env var '{envVarName}'");
break;
}
workingBranch = envVar;
Console.WriteLine($"TEST: Set working branch to '{workingBranch}' from env var '{TestRefEnvVar}'");
}
if (workingBranch == null)
@@ -93,6 +93,7 @@ namespace Tgstation.Server.Tests
{
args.Add($"Security:OAuth:{I}:ClientId=Fake");
args.Add($"Security:OAuth:{I}:ClientSecret=Faker");
args.Add($"Security:OAuth:{I}:Url=https://fakest.com");
}
// SPECIFICALLY DELETE THE DEV APPSETTINGS, WE DON'T WANT IT IN THE WAY