From 3b4bd2d703adbd4fc97a9b17476b4a497b05002a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 12:34:09 -0500 Subject: [PATCH 01/13] Adds URL setting to OAuthConfiguration --- README.md | 7 +++- .../Configuration/OAuthConfiguration.cs | 32 ++-------------- .../Configuration/OAuthConfigurationBase.cs | 37 +++++++++++++++++++ .../Security/OAuth/DiscordTokenRequest.cs | 4 +- .../Security/OAuth/OAuthTokenRequest.cs | 4 +- .../Security/OAuth/TGForumsOAuthValidator.cs | 2 +- 6 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs diff --git a/README.md b/README.md index 922fbab65d..c2cb3d211d 100644 --- a/README.md +++ b/README.md @@ -133,11 +133,16 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Security:OAuth`: Sets the OAuth client ID and secret for a given ``. 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)", + "Url": "...", (Used with certain providers) + "ClientId": "...", "ClientSecret": "..." } ``` +The following providers use the `Url` setting: + +- `TGForums`: Used as the OAuth redirect url. + ### Database Configuration If using a MariaDB/MySQL server, our client library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk. diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs index b0dc12fc21..5d865c76ed 100644 --- a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs @@ -1,37 +1,13 @@ -using System; - namespace Tgstation.Server.Host.Configuration { /// - /// OAuth options. + /// OAuth configuration options. /// - class OAuthConfiguration + sealed class OAuthConfiguration : OAuthConfigurationBase { /// - /// The client ID. + /// The redirect or server URL. Not used by all providers. /// - public string ClientId { get; set; } - - /// - /// The client secret. - /// - public string ClientSecret { get; set; } - - /// - /// Initializes a new instance of the . - /// - public OAuthConfiguration() { } - - /// - /// Initializes a new instance of the . - /// - /// The to copy settings from. - public OAuthConfiguration(OAuthConfiguration oAuthConfiguration) - { - if (oAuthConfiguration == null) - throw new ArgumentNullException(nameof(oAuthConfiguration)); - ClientId = oAuthConfiguration.ClientId; - ClientSecret = oAuthConfiguration.ClientSecret; - } + public string Url { get; set; } } } diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs new file mode 100644 index 0000000000..6fb4fda5fb --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs @@ -0,0 +1,37 @@ +using System; + +namespace Tgstation.Server.Host.Configuration +{ + /// + /// Base OAuth options. + /// + abstract class OAuthConfigurationBase + { + /// + /// The client ID. + /// + public string ClientId { get; set; } + + /// + /// The client secret. + /// + public string ClientSecret { get; set; } + + /// + /// Initializes a new instance of the . + /// + public OAuthConfigurationBase() { } + + /// + /// Initializes a new instance of the . + /// + /// The to copy settings from. + public OAuthConfigurationBase(OAuthConfigurationBase oAuthConfiguration) + { + if (oAuthConfiguration == null) + throw new ArgumentNullException(nameof(oAuthConfiguration)); + ClientId = oAuthConfiguration.ClientId; + ClientSecret = oAuthConfiguration.ClientSecret; + } + } +} diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs index fdd6dcc393..a3ee43c704 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs @@ -21,9 +21,9 @@ namespace Tgstation.Server.Host.Security.OAuth /// /// Initializes a new instance of the . /// - /// The for the . + /// The for the . /// The OAuth code for the . - public DiscordTokenRequest(OAuthConfiguration oAuthConfiguration, string code) + public DiscordTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code) : base(oAuthConfiguration, code) { GrantType = "authorization_code"; diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs index 7f03023625..1db7a8f692 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Security.OAuth /// /// Generic OAuth token request. /// - class OAuthTokenRequest : OAuthConfiguration + class OAuthTokenRequest : OAuthConfigurationBase { /// /// The OAuth code. @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Security.OAuth /// /// The to build from. /// The OAuth code received from the browser. - public OAuthTokenRequest(OAuthConfiguration oAuthConfiguration, string code) + public OAuthTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code) : base(oAuthConfiguration) { Code = code ?? throw new ArgumentNullException(nameof(code)); diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs index afdd635974..775551af97 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs @@ -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.Url)}" }; using var request = new HttpRequestMessage(HttpMethod.Get, builder.Uri); From 3168eded05065353d3bcd6e1347ad2e6174ff176 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 12:40:04 -0500 Subject: [PATCH 02/13] Refactors Scope into base OAuthTokenRequest --- .../Security/OAuth/DiscordTokenRequest.cs | 8 +------- .../Security/OAuth/OAuthTokenRequest.cs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs index a3ee43c704..cf477ba156 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs @@ -13,21 +13,15 @@ namespace Tgstation.Server.Host.Security.OAuth /// public string GrantType { get; } - /// - /// The 'scope' field. - /// - public string Scope { get; } - /// /// Initializes a new instance of the . /// /// The for the . /// The OAuth code for the . public DiscordTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code) - : base(oAuthConfiguration, code) + : base(oAuthConfiguration, code, "identify") { GrantType = "authorization_code"; - Scope = "identify"; } } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs index 1db7a8f692..cad2885746 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs @@ -9,19 +9,26 @@ namespace Tgstation.Server.Host.Security.OAuth class OAuthTokenRequest : OAuthConfigurationBase { /// - /// The OAuth code. + /// The OAuth code received from the browser. /// public string Code { get; } /// - /// Initializes a new instance of the + /// The scopes being requested. + /// + public string Scope { get; } + + /// + /// Initializes a new instance of the . /// /// The to build from. - /// The OAuth code received from the browser. - public OAuthTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code) + /// The value of . + /// The value of + public OAuthTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code, string scope) : base(oAuthConfiguration) { Code = code ?? throw new ArgumentNullException(nameof(code)); + Scope = scope ?? throw new ArgumentNullException(nameof(scope)); } } } From d524d06b5a932c2cd45b5fef07ead3aa2f44091b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 12:52:33 -0500 Subject: [PATCH 03/13] Add URL OAuth config to integration tests --- tests/Tgstation.Server.Tests/TestingServer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index 7eee0642a1..3459c2a11a 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -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 From a7026df1956e852a8210350d40272d2337043cff Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 13:13:35 -0500 Subject: [PATCH 04/13] Fix being unable to read current OAuthConnections --- src/Tgstation.Server.Host/Models/User.cs | 4 +++- .../Security/AuthenticationContextFactory.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index 5d4783dcf0..547df857df 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -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(), }; /// diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index d74ef23d4a..04bf2e2062 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -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) From 36044c826c3b3f4aa207a1395d82295ef7329273 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 13:14:29 -0500 Subject: [PATCH 05/13] Use string enums for OAuthProviders in the API --- docs/API.dox | 6 +++--- src/Tgstation.Server.Api/Models/OAuthConnection.cs | 3 +++ src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 1 + src/Tgstation.Server.Client/Tgstation.Server.Client.csproj | 1 - 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/API.dox b/docs/API.dox index f33f901cb2..c4cec21c15 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -144,9 +144,9 @@ 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 @section api_perms Permissions diff --git a/src/Tgstation.Server.Api/Models/OAuthConnection.cs b/src/Tgstation.Server.Api/Models/OAuthConnection.cs index d684a04e2e..f68f7d2f16 100644 --- a/src/Tgstation.Server.Api/Models/OAuthConnection.cs +++ b/src/Tgstation.Server.Api/Models/OAuthConnection.cs @@ -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 /// /// The of the . /// ] + [JsonConverter(typeof(StringEnumConverter))] [EnumDataType(typeof(OAuthProvider))] public OAuthProvider Provider { get; set; } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 7b61d94fec..f971e5b548 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -37,6 +37,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 3070b75011..4ceb1f0844 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -31,7 +31,6 @@ - all runtime; build; native; contentfiles; analyzers From 3147ffd2bfe7cd7bde8a23c673b0e4912b482d63 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 14:06:43 -0500 Subject: [PATCH 06/13] Oauth cleanups --- README.md | 13 ++++++--- docs/API.dox | 8 +++--- .../Models/OAuthProvider.cs | 5 ++++ .../Models/OAuthProviderInfo.cs | 20 ++++++++++++++ .../Models/ServerInformation.cs | 4 +-- .../Configuration/OAuthConfiguration.cs | 11 ++++++-- .../Controllers/HomeController.cs | 2 +- .../Security/OAuth/BaseOAuthValidator.cs | 2 +- .../Security/OAuth/DiscordOAuthValidator.cs | 2 +- .../Security/OAuth/DiscordTokenRequest.cs | 27 ------------------- .../Security/OAuth/GenericOAuthValidator.cs | 8 +++++- .../Security/OAuth/GitHubOAuthValidator.cs | 12 +++++++-- .../Security/OAuth/IOAuthProviders.cs | 4 +-- .../Security/OAuth/IOAuthValidator.cs | 4 +-- .../Security/OAuth/OAuthProviders.cs | 6 ++--- .../Security/OAuth/OAuthTokenRequest.cs | 17 ++++++++++-- .../Security/OAuth/TGForumsOAuthValidator.cs | 15 ++++++++--- 17 files changed, 102 insertions(+), 58 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs delete mode 100644 src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs diff --git a/README.md b/README.md index c2cb3d211d..26a84e81dd 100644 --- a/README.md +++ b/README.md @@ -133,15 +133,20 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Security:OAuth`: Sets the OAuth client ID and secret for a given ``. 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":{ - "Url": "...", (Used with certain providers) "ClientId": "...", - "ClientSecret": "..." + "ClientSecret": "...", + "RedirectUrl": "...", (Used with certain providers) + "ServerUrl": "...", (Used with certain providers) } ``` +The following providers use the `RedirectUrl` setting: -The following providers use the `Url` setting: +- GitHub +- TGForums -- `TGForums`: Used as the OAuth redirect url. +The following providers use the `ServerUrl` setting: + +- None so far ### Database Configuration diff --git a/docs/API.dox b/docs/API.dox index c4cec21c15..5507fa7f83 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -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: @@ -391,7 +391,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} { diff --git a/src/Tgstation.Server.Api/Models/OAuthProvider.cs b/src/Tgstation.Server.Api/Models/OAuthProvider.cs index 8d5a9eb354..0031ed0f79 100644 --- a/src/Tgstation.Server.Api/Models/OAuthProvider.cs +++ b/src/Tgstation.Server.Api/Models/OAuthProvider.cs @@ -19,5 +19,10 @@ namespace Tgstation.Server.Api.Models /// https://tgstation13.org /// TGForums, + + /// + /// https://www.keycloak.org + /// + Keycloak, } } diff --git a/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs b/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs new file mode 100644 index 0000000000..10466c3204 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/OAuthProviderInfo.cs @@ -0,0 +1,20 @@ +using System; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Public information about a given . + /// + public sealed class OAuthProviderInfo + { + /// + /// The client ID. + /// + public string? ClientId { get; set; } + + /// + /// The redirect URL. + /// + public Uri? RedirectUri { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/ServerInformation.cs b/src/Tgstation.Server.Api/Models/ServerInformation.cs index a2d8958845..2111302237 100644 --- a/src/Tgstation.Server.Api/Models/ServerInformation.cs +++ b/src/Tgstation.Server.Api/Models/ServerInformation.cs @@ -24,8 +24,8 @@ namespace Tgstation.Server.Api.Models public Version? DMApiVersion { get; set; } /// - /// Map of to the server's associated client IDs for them. + /// Map of to the for them. /// - public IDictionary? OAuthProviderClientIds { get; set; } + public IDictionary? OAuthProviderInfos { get; set; } } } diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs index 5d865c76ed..b12e239911 100644 --- a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs @@ -1,3 +1,5 @@ +using System; + namespace Tgstation.Server.Host.Configuration { /// @@ -6,8 +8,13 @@ namespace Tgstation.Server.Host.Configuration sealed class OAuthConfiguration : OAuthConfigurationBase { /// - /// The redirect or server URL. Not used by all providers. + /// The client redirect URL. Not used by all providers. /// - public string Url { get; set; } + public Uri ServerUrl { get; set; } + + /// + /// The authentication server URL. Not used by all providers. + /// + public Uri RedirectUrl { get; set; } } } diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 9d09033105..fd4dd4d7f8 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -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) }); } diff --git a/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs index 68cc17cf23..4cb1995f59 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/BaseOAuthValidator.cs @@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Security.OAuth } /// - public abstract Task GetClientId(CancellationToken cancellationToken); + public abstract Task GetProviderInfo(CancellationToken cancellationToken); /// public abstract Task ValidateResponseCode(string code, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs index f0fe8591f5..37d055d08b 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/DiscordOAuthValidator.cs @@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.Security.OAuth protected override Uri UserInformationUrl => new Uri("https://discord.com/api/users/@me"); /// - protected override OAuthTokenRequest CreateTokenRequest(string code) => new DiscordTokenRequest(OAuthConfiguration, code); + protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "identify"); /// protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token; diff --git a/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs deleted file mode 100644 index cf477ba156..0000000000 --- a/src/Tgstation.Server.Host/Security/OAuth/DiscordTokenRequest.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Tgstation.Server.Host.Configuration; - -namespace Tgstation.Server.Host.Security.OAuth -{ - /// - /// for Discord. - /// - /// See https://discord.com/developers/docs/topics/oauth2 - sealed class DiscordTokenRequest : OAuthTokenRequest - { - /// - /// The 'grant_type' field. - /// - public string GrantType { get; } - - /// - /// Initializes a new instance of the . - /// - /// The for the . - /// The OAuth code for the . - public DiscordTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code) - : base(oAuthConfiguration, code, "identify") - { - GrantType = "authorization_code"; - } - } -} diff --git a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs index 8deff22537..98294d46d6 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs @@ -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; @@ -119,6 +120,11 @@ namespace Tgstation.Server.Host.Security.OAuth } /// - public override Task GetClientId(CancellationToken cancellationToken) => Task.FromResult(OAuthConfiguration.ClientId); + public override Task GetProviderInfo(CancellationToken cancellationToken) => Task.FromResult( + new OAuthProviderInfo + { + ClientId = OAuthConfiguration.ClientId, + RedirectUri = OAuthConfiguration.RedirectUrl + }); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs index b4ee1354e6..c678568ebe 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs @@ -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 } /// - public Task GetClientId(CancellationToken cancellationToken) => Task.FromResult(oAuthConfiguration.ClientId); + public Task GetProviderInfo(CancellationToken cancellationToken) => Task.FromResult( + new OAuthProviderInfo + { + ClientId = oAuthConfiguration.ClientId, + RedirectUri = oAuthConfiguration.RedirectUrl + }); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs index 66af75b5af..06cae4e96e 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/IOAuthProviders.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Security.OAuth /// Gets a of the provider client IDs. /// /// The for the operation. - /// A resulting in a anew of the active provider client IDs. - Task> ClientIds(CancellationToken cancellationToken); + /// A resulting in a anew of the active s. + Task> ProviderInfos(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs index be33dcb992..be8df49788 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs @@ -15,11 +15,11 @@ namespace Tgstation.Server.Host.Security.OAuth OAuthProvider Provider { get; } /// - /// Gets the OAuth client ID of validator. + /// Gets the of validator. /// /// The for the operation. /// A resulting in the client ID of the validator on success, on failure. - Task GetClientId(CancellationToken cancellationToken); + Task GetProviderInfo(CancellationToken cancellationToken); /// /// Validate a given OAuth response . diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs index 91be97f0e2..9087aad25b 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs @@ -58,7 +58,7 @@ namespace Tgstation.Server.Host.Security.OAuth loggerFactory.CreateLogger(), discordConfig)); - if(securityConfiguration.OAuth.TryGetValue(OAuthProvider.TGForums, out var tgConfig)) + if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.TGForums, out var tgConfig)) validatorsBuilder.Add( new TGForumsOAuthValidator( httpClientFactory, @@ -73,11 +73,11 @@ namespace Tgstation.Server.Host.Security.OAuth public IOAuthValidator GetValidator(OAuthProvider oAuthProvider) => validators.FirstOrDefault(x => x.Provider == oAuthProvider); /// - public async Task> ClientIds(CancellationToken cancellationToken) + public async Task> 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); diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs index cad2885746..eb715953c6 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthTokenRequest.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Security.OAuth /// /// Generic OAuth token request. /// - class OAuthTokenRequest : OAuthConfigurationBase + sealed class OAuthTokenRequest : OAuthConfigurationBase { /// /// The OAuth code received from the browser. @@ -18,17 +18,30 @@ namespace Tgstation.Server.Host.Security.OAuth /// public string Scope { get; } + /// + /// The OAuth redirect URI. + /// + public Uri RedirectUri { get; } + + /// + /// The OAuth grant type. + /// + public string GrantType { get; } + /// /// Initializes a new instance of the . /// /// The to build from. /// The value of . /// The value of - public OAuthTokenRequest(OAuthConfigurationBase oAuthConfiguration, string code, string scope) + 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"; } } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs index 775551af97..4e6028987e 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs @@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Security.OAuth } /// - public override async Task GetClientId(CancellationToken cancellationToken) + public override async Task 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.Url)}" + 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) { From 4a070e39a7fe4c1c3208bce71d526cd7997b4f89 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 14:13:09 -0500 Subject: [PATCH 07/13] Adds a single log line --- .../Security/OAuth/GenericOAuthValidator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs index 98294d46d6..ad0de1e980 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs @@ -96,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); From 73a88245b17605c9d9cb49d6592e417876f30f2b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 14:14:11 -0500 Subject: [PATCH 08/13] Adds Keycloak OAuth support --- README.md | 3 +- docs/API.dox | 1 + .../Security/OAuth/KeycloakOAuthValidator.cs | 54 +++++++++++++++++++ .../Security/OAuth/OAuthProviders.cs | 8 +++ src/Tgstation.Server.Host/appsettings.json | 3 +- 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs diff --git a/README.md b/README.md index 26a84e81dd..841fdb4e12 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,11 @@ The following providers use the `RedirectUrl` setting: - GitHub - TGForums +- Keycloak The following providers use the `ServerUrl` setting: -- None so far +- Keycloak ### Database Configuration diff --git a/docs/API.dox b/docs/API.dox index 5507fa7f83..927d3cd0c5 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -147,6 +147,7 @@ You will be granted a bearer token as in basic auth. This will have an extended - 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 diff --git a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs new file mode 100644 index 0000000000..61e08a56ab --- /dev/null +++ b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs @@ -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 +{ + /// + /// OAuth validator for Keycloak. + /// + sealed class KeycloakOAuthValidator : GenericOAuthValidator + { + /// + public override OAuthProvider Provider => OAuthProvider.Keycloak; + + /// + protected override Uri TokenUrl => new Uri($"{BaseProtocolPath}/token"); + + /// + protected override Uri UserInformationUrl => new Uri($"{BaseProtocolPath}/userinfo"); + + /// + /// Base path to the server's OAuth endpoint. + /// + string BaseProtocolPath => $"{OAuthConfiguration.ServerUrl}/protocol/openid-connect"; + + /// + /// Initializes a new instance of the . + /// + /// The for the . + /// The for the . + /// The for the . + /// The for the . + public KeycloakOAuthValidator( + IHttpClientFactory httpClientFactory, + IAssemblyInformationProvider assemblyInformationProvider, + ILogger logger, + OAuthConfiguration oAuthConfiguration) + : base(httpClientFactory, assemblyInformationProvider, logger, oAuthConfiguration) + { + } + + /// + protected override OAuthTokenRequest CreateTokenRequest(string code) => new OAuthTokenRequest(OAuthConfiguration, code, "openid"); + + /// + protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token; + + /// + protected override string DecodeUserInformationPayload(dynamic responseJson) => responseJson.sub; + } +} diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs index 9087aad25b..e8133706b2 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs @@ -66,6 +66,14 @@ namespace Tgstation.Server.Host.Security.OAuth loggerFactory.CreateLogger(), tgConfig)); + if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.Keycloak, out var keyCloakConfig)) + validatorsBuilder.Add( + new KeycloakOAuthValidator( + httpClientFactory, + assemblyInformationProvider, + loggerFactory.CreateLogger(), + keyCloakConfig)); + validators = validatorsBuilder; } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 00506b5762..b0df570838 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -58,7 +58,8 @@ "OAuth": { "GitHub": null, "Discord": null, - "TGForums": null + "TGForums": null, + "Keycloak": null } } } From 20f55160bd307f42e896940a8d868be1c384d6ce Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 14:19:23 -0500 Subject: [PATCH 09/13] Removes an annoying trailing `/` --- docs/API.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API.dox b/docs/API.dox index 927d3cd0c5..0a27032d8e 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -144,7 +144,7 @@ You will be granted a bearer token as in basic auth. This will have an extended @subsubsection api_auth_o_providers Supported Providers -- GitHub: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/ +- 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 From e2da8b53716722eda165524d9febc55c487291e4 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 15:34:05 -0500 Subject: [PATCH 10/13] Fix setting TGS4_GITHUB_REF for PRs --- .github/workflows/ci-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-suite.yml b/.github/workflows/ci-suite.yml index 15c75bb15d..c316b5b649 100644 --- a/.github/workflows/ci-suite.yml +++ b/.github/workflows/ci-suite.yml @@ -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' }} From 3716c84664d0e50cb464b785e60e4442c023fa70 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 15:46:18 -0500 Subject: [PATCH 11/13] Fix a potential test error dropping --- tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs b/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs index 3bf72f06e1..32c94dc8ae 100644 --- a/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs @@ -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); From d21143da4a3df99b6e9063ef859f4f152578cb87 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 16:12:14 -0500 Subject: [PATCH 12/13] Fix test branch parsing --- .../Instance/RepositoryTest.cs | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs index 02b3251622..1bdb2bb7fb 100644 --- a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs @@ -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 - { - "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) From daebe5eac893d79f067690f6c99bda1c1b027fab Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 7 Dec 2020 16:48:39 -0500 Subject: [PATCH 13/13] War on TGS4_GITHUB_REF EP4 --- .github/workflows/ci-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-suite.yml b/.github/workflows/ci-suite.yml index c316b5b649..474aabc0f1 100644 --- a/.github/workflows/ci-suite.yml +++ b/.github/workflows/ci-suite.yml @@ -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' }}