diff --git a/Update-OctopusReferences.ps1 b/Update-OctopusReferences.ps1
new file mode 100644
index 0000000..f12a077
--- /dev/null
+++ b/Update-OctopusReferences.ps1
@@ -0,0 +1,8 @@
+# Run this script after installing a new version of Octopus Server, but before starting it (due to file lock and dependency load issues).
+
+$OctopusServerBinaryLocation = "E:\Program Files\Octopus Deploy\Octopus" # Make sure this points to where you install the Octopus Server binaries
+$OctopusServerCustomExtensionsLocation = "C:\ProgramData\Octopus\CustomExtensions\" # This should always point to where the Octopus Server CustomExtenions folder lives
+
+Copy-Item "$OctopusServerBinaryLocation\Octopus.Data.dll" -Destination "$OctopusServerCustomExtensionsLocation" -Force
+Copy-Item "$OctopusServerBinaryLocation\Octopus.Server.Extensibility.Authentication.dll" -Destination "$OctopusServerCustomExtensionsLocation" -Force
+Copy-Item "$OctopusServerBinaryLocation\BuiltInExtensions\Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.dll" -Destination "$OctopusServerCustomExtensionsLocation" -Force
diff --git a/source/Client.AzureAD/Client.AzureAD.csproj b/source/Client.AzureAD/Client.AzureAD.csproj
index 0fee24f..9d89ea7 100644
--- a/source/Client.AzureAD/Client.AzureAD.csproj
+++ b/source/Client.AzureAD/Client.AzureAD.csproj
@@ -24,5 +24,9 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
diff --git a/source/Client.AzureAD/Configuration/AzureADConfigurationResource.cs b/source/Client.AzureAD/Configuration/AzureADConfigurationResource.cs
index 6e37802..68cf93b 100644
--- a/source/Client.AzureAD/Configuration/AzureADConfigurationResource.cs
+++ b/source/Client.AzureAD/Configuration/AzureADConfigurationResource.cs
@@ -1,6 +1,7 @@
using System.ComponentModel;
using Octopus.Client.Extensibility.Attributes;
using Octopus.Client.Extensibility.Authentication.OpenIDConnect.Configuration;
+using Octopus.Client.Model;
namespace Octopus.Client.Extensibility.Authentication.AzureAD.Configuration
{
@@ -16,5 +17,10 @@ public AzureADConfigurationResource()
[Description("Tell Octopus how to find the roles/groups in the security token from Azure Active Directory (usually \"roles\" or \"groups\")")]
[Writeable]
public string RoleClaimType { get; set; }
+
+ [DisplayName("Client Access Key")]
+ [Description("The Azure app registration secret access key. This is used for authenticating against the Azure GraphAPI for group overage lookups. If left blank it will disable Azure GraphAPI lookups. [Learn more](https://github.com/StephenShamakian/OpenIDConnectAuthenticationProviders#readme)")]
+ [Writeable]
+ public SensitiveValue ClientKey { get; set; }
}
}
\ No newline at end of file
diff --git a/source/Client.GoogleApps/Client.GoogleApps.csproj b/source/Client.GoogleApps/Client.GoogleApps.csproj
index 1751093..c99c2b4 100644
--- a/source/Client.GoogleApps/Client.GoogleApps.csproj
+++ b/source/Client.GoogleApps/Client.GoogleApps.csproj
@@ -23,5 +23,9 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
diff --git a/source/Client.OctopusID/Client.OctopusID.csproj b/source/Client.OctopusID/Client.OctopusID.csproj
index 62e81e2..51f7e7b 100644
--- a/source/Client.OctopusID/Client.OctopusID.csproj
+++ b/source/Client.OctopusID/Client.OctopusID.csproj
@@ -23,5 +23,9 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
diff --git a/source/Client.Okta/Client.Okta.csproj b/source/Client.Okta/Client.Okta.csproj
index c3956d5..59efa9b 100644
--- a/source/Client.Okta/Client.Okta.csproj
+++ b/source/Client.Okta/Client.Okta.csproj
@@ -23,5 +23,9 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
diff --git a/source/Client.OpenIDConnect/Client.OpenIDConnect.csproj b/source/Client.OpenIDConnect/Client.OpenIDConnect.csproj
index 3a9c2c6..ceb15fa 100644
--- a/source/Client.OpenIDConnect/Client.OpenIDConnect.csproj
+++ b/source/Client.OpenIDConnect/Client.OpenIDConnect.csproj
@@ -15,13 +15,13 @@
https://github.com/OctopusDeploy/OpenIDConnectAuthenticationProviders
-
-
-
-
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
diff --git a/source/Server.AzureAD/AzureADExtension.cs b/source/Server.AzureAD/AzureADExtension.cs
index 71bf19d..925779e 100644
--- a/source/Server.AzureAD/AzureADExtension.cs
+++ b/source/Server.AzureAD/AzureADExtension.cs
@@ -20,7 +20,7 @@
namespace Octopus.Server.Extensibility.Authentication.AzureAD
{
- [OctopusPlugin("AzureAD", "Octopus Deploy")]
+ [OctopusPlugin("AzureAD - GraphAPI Support", "Octopus Deploy (Modified by: Stephen Shamakian)")]
public class AzureADExtension : OpenIDConnectExtension, IOctopusExtension
{
public override void Load(ContainerBuilder builder)
diff --git a/source/Server.AzureAD/Configuration/AzureADConfiguration.cs b/source/Server.AzureAD/Configuration/AzureADConfiguration.cs
index adfa681..e91c964 100644
--- a/source/Server.AzureAD/Configuration/AzureADConfiguration.cs
+++ b/source/Server.AzureAD/Configuration/AzureADConfiguration.cs
@@ -1,4 +1,5 @@
-using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
+using Octopus.Data.Model;
+using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
{
@@ -10,5 +11,7 @@ public AzureADConfiguration() : base(AzureADConfigurationStore.SingletonId, "Azu
{
RoleClaimType = DefaultRoleClaimType;
}
+
+ public SensitiveString? ClientKey { get; set; }
}
}
\ No newline at end of file
diff --git a/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs b/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs
index f2f32ef..924be00 100644
--- a/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs
+++ b/source/Server.AzureAD/Configuration/AzureADConfigurationResource.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
+using Octopus.Server.MessageContracts;
using Octopus.Server.MessageContracts.Attributes;
namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
@@ -11,5 +12,10 @@ class AzureADConfigurationResource : OpenIDConnectConfigurationResource
[Description("Tell Octopus how to find the roles/groups in the security token from Azure Active Directory (usually \"roles\" or \"groups\")")]
[Writeable]
public string? RoleClaimType { get; set; }
+
+ [DisplayName("Client Access Key")]
+ [Description("The Azure app registration secret access key. This is used for authenticating against the Azure GraphAPI for group overage lookups. If left blank it will disable Azure GraphAPI lookups. [Learn more](https://github.com/StephenShamakian/OpenIDConnectAuthenticationProviders#readme)")]
+ [Writeable]
+ public SensitiveValue? ClientKey { get; set; }
}
}
\ No newline at end of file
diff --git a/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs b/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs
index 9f05b52..61ce749 100644
--- a/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs
+++ b/source/Server.AzureAD/Configuration/AzureADConfigurationSettings.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Octopus.Data.Model;
using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
using Octopus.Server.Extensibility.Extensions.Infrastructure.Configuration;
@@ -21,6 +22,7 @@ public override IEnumerable GetConfigurationValues()
yield return configurationValue;
}
yield return new ConfigurationValue($"Octopus.{ConfigurationDocumentStore.ConfigurationSettingsName}.RoleClaimType", ConfigurationDocumentStore.GetRoleClaimType(), ConfigurationDocumentStore.GetIsEnabled() && ConfigurationDocumentStore.GetRoleClaimType() != AzureADConfiguration.DefaultRoleClaimType, "Role Claim Type");
+ yield return new ConfigurationValue($"Octopus.{ConfigurationDocumentStore.ConfigurationSettingsName}.ClientKey", ConfigurationDocumentStore.GetClientKey(), ConfigurationDocumentStore.GetIsEnabled(), "Client Access Key");
}
}
diff --git a/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs b/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs
index 5c666d6..f30a99f 100644
--- a/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs
+++ b/source/Server.AzureAD/Configuration/AzureADConfigurationStore.cs
@@ -1,4 +1,6 @@
-using Octopus.Data.Storage.Configuration;
+using Octopus.Data.Model;
+using Octopus.Data.Storage.Configuration;
+using Octopus.Diagnostics;
using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
@@ -6,14 +8,26 @@ namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
class AzureADConfigurationStore : OpenIDConnectConfigurationWithRoleStore, IAzureADConfigurationStore
{
public const string SingletonId = "authentication-aad";
+ ISystemLog log;
public override string Id => SingletonId;
public override string ConfigurationSettingsName => "AzureAD";
public AzureADConfigurationStore(
- IConfigurationStore configurationStore) : base(configurationStore)
+ IConfigurationStore configurationStore, ISystemLog log) : base(configurationStore)
{
+ this.log = log;
}
+
+ public SensitiveString? GetClientKey() => GetProperty(doc => doc.ClientKey);
+
+ public void SetClientKey(SensitiveString? key) => SetProperty(doc =>
+ {
+ if (!string.IsNullOrEmpty(key?.Value))
+ log.WithSensitiveValue(key.Value);
+
+ doc.ClientKey = key;
+ });
}
}
\ No newline at end of file
diff --git a/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs b/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs
index a494459..f601891 100644
--- a/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs
+++ b/source/Server.AzureAD/Configuration/AzureADConfigureCommands.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Octopus.Data.Model;
using Octopus.Diagnostics;
using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
using Octopus.Server.Extensibility.Extensions.Infrastructure.Configuration;
@@ -9,12 +10,15 @@ namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
{
class AzureADConfigureCommands : OpenIDConnectConfigureCommands
{
+ readonly ISystemLog log;
+
public AzureADConfigureCommands(
ISystemLog log,
Lazy configurationStore,
Lazy webPortalConfigurationStore)
: base(log, configurationStore, webPortalConfigurationStore)
{
+ this.log = log;
}
protected override string ConfigurationSettingsName => "azureAD";
@@ -30,6 +34,19 @@ public override IEnumerable GetOptions()
ConfigurationStore.Value.SetRoleClaimType(v);
Log.Info($"{ConfigurationSettingsName} RoleClaimType set to: {v}");
});
+ yield return new ConfigureCommandOption($"{ConfigurationSettingsName}ClientKey=", "The App Registration secret access key. Used for authenticating against the GraphAPI for group overage lookups.", v =>
+ {
+ if (!string.IsNullOrEmpty(v))
+ {
+ ConfigurationStore.Value.SetClientKey(v.ToSensitiveString());
+ log.Info("Azure AD Graph API Client Key set to provided value");
+ }
+ else
+ {
+ ConfigurationStore.Value.SetClientKey(null);
+ log.Info("Azure AD Graph API Client Key set to null (anonymous bind)");
+ }
+ });
}
}
}
\ No newline at end of file
diff --git a/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs b/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs
index 9f1b8c5..bb0fedf 100644
--- a/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs
+++ b/source/Server.AzureAD/Configuration/IAzureADConfigurationStore.cs
@@ -1,8 +1,13 @@
-using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
+using Octopus.Data.Model;
+using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Configuration;
namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
{
interface IAzureADConfigurationStore : IOpenIDConnectConfigurationWithRoleStore
{
+
+ SensitiveString? GetClientKey();
+ void SetClientKey(SensitiveString? key);
+
}
}
\ No newline at end of file
diff --git a/source/Server.AzureAD/Configuration/MapFromAzureADConfigurationResourceToAzureADConfiguration.cs b/source/Server.AzureAD/Configuration/MapFromAzureADConfigurationResourceToAzureADConfiguration.cs
new file mode 100644
index 0000000..a282548
--- /dev/null
+++ b/source/Server.AzureAD/Configuration/MapFromAzureADConfigurationResourceToAzureADConfiguration.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Octopus.Core.Infrastructure.Mapping;
+using Octopus.Data.Model;
+
+namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
+{
+ class MapFromAzureADConfigurationResourceToAzureADConfiguration : IMapToNew, IMapToExisting
+ {
+ public async Task Map(AzureADConfigurationResource source, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ var target = new AzureADConfiguration();
+
+ target.IsEnabled = source.IsEnabled;
+ target.Issuer = source.Issuer;
+ target.RoleClaimType = source.RoleClaimType;
+ target.AllowAutoUserCreation = source.AllowAutoUserCreation ?? false;
+ target.ClientId = source.ClientId;
+
+ if (source.ClientSecret is { HasValue: true, NewValue: { } })
+ {
+ target.ClientSecret = source.ClientSecret.NewValue.ToSensitiveString();
+ }
+
+ if (source.ClientSecret is not { HasValue: true })
+ {
+ target.ClientSecret = null;
+ }
+
+ if (source.ClientKey is { HasValue: true, NewValue: { } })
+ {
+ target.ClientKey = source.ClientKey.NewValue.ToSensitiveString();
+ }
+
+ if (source.ClientKey is not { HasValue: true })
+ {
+ target.ClientKey = null;
+ }
+
+ return target;
+ }
+
+ public async Task Map(AzureADConfigurationResource source, AzureADConfiguration target, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+
+ target.IsEnabled = source.IsEnabled;
+ target.Issuer = source.Issuer;
+ target.RoleClaimType = source.RoleClaimType;
+ target.AllowAutoUserCreation = source.AllowAutoUserCreation ?? false;
+ target.ClientId = source.ClientId;
+
+ if (source.ClientSecret is { HasValue: true, NewValue: { } })
+ {
+ target.ClientSecret = source.ClientSecret.NewValue.ToSensitiveString();
+ }
+
+ if (source.ClientSecret is not { HasValue: true })
+ {
+ target.ClientSecret = null;
+ }
+
+ if (source.ClientKey is { HasValue: true, NewValue: { } })
+ {
+ target.ClientKey = source.ClientKey.NewValue.ToSensitiveString();
+ }
+
+ if (source.ClientKey is not { HasValue: true })
+ {
+ target.ClientKey = null;
+ }
+ }
+ }
+}
diff --git a/source/Server.AzureAD/Configuration/MapFromAzureADConfigurationToAzureADConfigurationResource.cs b/source/Server.AzureAD/Configuration/MapFromAzureADConfigurationToAzureADConfigurationResource.cs
new file mode 100644
index 0000000..d9c3354
--- /dev/null
+++ b/source/Server.AzureAD/Configuration/MapFromAzureADConfigurationToAzureADConfigurationResource.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Octopus.Core.Infrastructure.Mapping;
+using Octopus.Server.MessageContracts;
+
+namespace Octopus.Server.Extensibility.Authentication.AzureAD.Configuration
+{
+ class MapFromAzureADConfigurationToAzureADConfigurationResource : IMapToNew
+ {
+ public async Task Map(AzureADConfiguration source, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+
+ var target = new AzureADConfigurationResource();
+
+ target.Id = source.Id;
+ target.IsEnabled = source.IsEnabled;
+ target.AllowAutoUserCreation = source.AllowAutoUserCreation;
+ target.RoleClaimType = source.RoleClaimType;
+ target.Issuer = source.Issuer;
+ target.ClientId = source.ClientId;
+
+ if (source.ClientSecret == null)
+ {
+ target.ClientSecret = null;
+ }
+ else
+ {
+ target.ClientSecret = !string.IsNullOrWhiteSpace(source.ClientSecret?.Value) ? new SensitiveValue { HasValue = true } : new SensitiveValue { HasValue = false };
+ }
+
+ if (source.ClientKey == null)
+ {
+ target.ClientKey = null;
+ }
+ else
+ {
+ target.ClientKey = !string.IsNullOrWhiteSpace(source.ClientKey?.Value) ? new SensitiveValue { HasValue = true } : new SensitiveValue { HasValue = false };
+ }
+
+ return target;
+ }
+ }
+}
diff --git a/source/Server.AzureAD/Server.AzureAD.csproj b/source/Server.AzureAD/Server.AzureAD.csproj
index e3821e7..3dbecde 100644
--- a/source/Server.AzureAD/Server.AzureAD.csproj
+++ b/source/Server.AzureAD/Server.AzureAD.csproj
@@ -14,9 +14,9 @@
true
-
-
-
+
+ 1701;1702
+
@@ -24,8 +24,42 @@
-
-
-
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Newtonsoft.Json.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Core.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Data.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Autofac.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.Base.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Diagnostics.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Time.dll
+
+
diff --git a/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs b/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs
index 0a48b22..c6c9bc6 100644
--- a/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs
+++ b/source/Server.AzureAD/Tokens/AzureADAuthTokenHandler.cs
@@ -1,15 +1,238 @@
-using Octopus.Diagnostics;
+using Newtonsoft.Json;
+using Octopus.Data.Model;
+using Octopus.Diagnostics;
using Octopus.Server.Extensibility.Authentication.AzureAD.Configuration;
using Octopus.Server.Extensibility.Authentication.AzureAD.Issuer;
using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Issuer;
using Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Claims;
+using System.Threading.Tasks;
namespace Octopus.Server.Extensibility.Authentication.AzureAD.Tokens
{
class AzureADAuthTokenHandler : OpenIDConnectAuthTokenWithRolesHandler, IAzureADAuthTokenHandler
{
public AzureADAuthTokenHandler(ISystemLog log, IAzureADConfigurationStore configurationStore, IIdentityProviderConfigDiscoverer identityProviderConfigDiscoverer, IAzureADKeyRetriever keyRetriever) : base(log, configurationStore, identityProviderConfigDiscoverer, keyRetriever)
+ {}
+
+ protected class MicrosoftGraphResponse
+ {
+ public string? odata {get; set;}
+ public List? value { get; set; }
+ }
+
+ protected class MicrosoftGraphTokenResponse
+ {
+ public string? token_type { get; set; }
+ public int expires_in { get; set; }
+ public int ext_expires_in { get; set; }
+ public string? access_token { get; set; }
+ }
+
+ protected async Task FollowGroupApiCall(ClaimsPrincipal principal)
{
+ List groupObjectIds = new List();
+
+ string? clientId = ConfigurationStore.GetClientId();
+
+ if (string.IsNullOrWhiteSpace(clientId))
+ {
+ // Failed to get Access Token
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get App Registration Client ID from Octopus Configuration Store!");
+ return new string[0];
+ }
+
+ String? clientKey = ConfigurationStore.GetClientKey()?.Value;
+
+ if (String.IsNullOrWhiteSpace(clientKey))
+ {
+ // Failed to get Access Token
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get App Registration Client Key from Octopus Configuration Store!");
+ return new string[0];
+ }
+
+ string? tenantId = null;
+ if (principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid") != null)
+ {
+ tenantId = principal.Claims.FirstOrDefault(c => string.Equals(c.Type, "http://schemas.microsoft.com/identity/claims/tenantid", StringComparison.OrdinalIgnoreCase)).Value;
+ }
+ else
+ {
+ // Failed to get tenantId from claim data
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get AzureAD TenantID (tid) from claim data. Make sure tenantid is returned in the claim!");
+ return new string[0];
+ }
+
+ HttpClient client = new HttpClient();
+
+ // Get Access Token for GraphAPI
+ HttpRequestMessage requestToken = new HttpRequestMessage(HttpMethod.Post, "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token");
+
+ var body = new List>();
+ body.Add(new KeyValuePair("client_id", clientId));
+ body.Add(new KeyValuePair("scope", "https://graph.microsoft.com/.default"));
+ body.Add(new KeyValuePair("client_secret", clientKey));
+ body.Add(new KeyValuePair("grant_type", "client_credentials"));
+
+ requestToken.Content = new FormUrlEncodedContent(body);
+ HttpResponseMessage responseToken = await client.SendAsync(requestToken);
+
+ // Endpoint returns JSON with an array of Group ObjectIDs
+ if (responseToken.IsSuccessStatusCode)
+ {
+
+ string responseTokenContent = await responseToken.Content.ReadAsStringAsync();
+ MicrosoftGraphTokenResponse tokenResult = JsonConvert.DeserializeObject(responseTokenContent);
+
+ string accessToken;
+
+ if ((tokenResult.access_token) != null)
+ {
+ accessToken = tokenResult.access_token;
+ }
+ else
+ {
+ // Failed to get Access Token
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get user's Access Token!");
+ return new string[0];
+ }
+
+ string? userObjectId = null;
+ if (principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier") != null)
+ {
+ userObjectId = principal.Claims.FirstOrDefault(c => string.Equals(c.Type, "http://schemas.microsoft.com/identity/claims/objectidentifier", StringComparison.OrdinalIgnoreCase)).Value;
+ }
+ else
+ {
+ // Failed to get userObjectId from claim data
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get AzureAD User Object ID (oid) from claim data. Make sure the user oid is returned in the claim!");
+ return new string[0];
+ }
+
+ string requestUrl = "https://graph.microsoft.com/v1.0/" + tenantId + "/users/" + userObjectId + "/getMemberObjects";
+
+ // Get Group Membership list from GraphAPI
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
+ StringContent content = new StringContent("{\"securityEnabledOnly\": \"false\"}");
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+ request.Content = content;
+ HttpResponseMessage response = await client.SendAsync(request);
+
+ // Endpoint returns JSON with an array of Group ObjectIDs
+ if (response.IsSuccessStatusCode)
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ MicrosoftGraphResponse groupsResult = JsonConvert.DeserializeObject(responseContent);
+
+ if ((groupsResult.value) != null)
+ {
+ foreach (string groupObjectID in groupsResult.value)
+ {
+ groupObjectIds.Add(groupObjectID);
+ }
+ }
+ else
+ {
+ // Failed to get Group Memberships
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get list of groups from the AzureAD Graph API!");
+ return new string[0];
+ }
+
+ string[] groups = groupObjectIds.ToArray();
+
+ return groups;
+ }
+ else
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+
+ // Failed to get Group Memberships
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get group membership via the AzureAD Graph API!");
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - " + responseContent);
+ return new string[0];
+ }
+ }
+ else
+ {
+ string responseTokenContent = await responseToken.Content.ReadAsStringAsync();
+
+ // Failed to get Auth Token
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - Failed to get Auth Token for group membership API!");
+ Log.Error("+++ AzureAD-GraphAPI: ERROR - " + responseTokenContent);
+ return new string[0];
+ }
+ }
+
+
+ protected override string[] GetProviderGroupIds(ClaimsPrincipal principal)
+ {
+ var roleClaimType = ConfigurationStore.GetRoleClaimType();
+
+ if (string.IsNullOrWhiteSpace(roleClaimType))
+ {
+ return new string[0];
+ }
+
+ // Get Octopus AzureAD COnfiguration - Client Access Key
+ String? clientKey = ConfigurationStore.GetClientKey()?.Value;
+
+
+ // Retrieve "_claims_names" token value if set, if not null
+ string? claimNames = null;
+ if (principal.FindFirst("_claim_names") != null)
+ {
+ claimNames = principal.Claims.FirstOrDefault(c => string.Equals(c.Type, "_claim_names", StringComparison.OrdinalIgnoreCase)).Value;
+ }
+
+
+ // Get some additional claim data for better logging
+ string claimUsersEmail = "(Email token not present in claim!)";
+ if (principal.FindFirst(ClaimTypes.Email) != null)
+ {
+ claimUsersEmail = principal.Claims.FirstOrDefault(c => string.Equals(c.Type, ClaimTypes.Email, StringComparison.OrdinalIgnoreCase)).Value;
+ }
+
+ string claimUsersOid = "(Object ID (oid) token not present in claim!)";
+ if (principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier") != null)
+ {
+ claimUsersOid = principal.Claims.FirstOrDefault(c => string.Equals(c.Type, "http://schemas.microsoft.com/identity/claims/objectidentifier", StringComparison.OrdinalIgnoreCase)).Value;
+ }
+
+ // Only follow the Microsoft GraphAPI for group membership If:
+ // - Octopus AzureAD Config Client Access Key is set
+ // - claimNames is not null
+ // - claimNames has the value of: {"groups":"src1"}
+ // Else look for group membership in JWT token
+ if ((!String.IsNullOrWhiteSpace(clientKey)) && (!String.IsNullOrWhiteSpace(claimNames)) && (claimNames == "{\"groups\":\"src1\"}"))
+ {
+
+ // If this claim has the "_claim_names" present this means this user is over the 150/200 group limit in the token. We need to follow the Microsoft Azure Graph API. But only if the Client Key is set in the AzureAD Octopus configuration.
+ Log.Info("+++ AzureAD-GraphAPI: UserAuth - Using Azure GraphAPI group lookup endpoint - ("+ claimUsersEmail + " - "+ claimUsersOid + ")");
+
+ return FollowGroupApiCall(principal).Result;
+
+ }
+ else
+ {
+
+ // the groups Ids consist of external Role and Group identifiers. We always load ClaimTypes.Role claims
+ // as external identifiers, and then also based on a custom claim specified by the provider.
+ Log.Info("+++ AzureAD-GraphAPI: UserAuth - Using JWT token groups - (" + claimUsersEmail + " - " + claimUsersOid + ")");
+
+ var groups = principal.FindAll(ClaimTypes.Role)
+ .Concat(principal.FindAll(roleClaimType))
+ .Select(c => c.Value).ToArray();
+
+ return groups;
+
+ }
+
}
}
}
\ No newline at end of file
diff --git a/source/Server.AzureAD/Web/AzureADUserAuthenticatedAction.cs b/source/Server.AzureAD/Web/AzureADUserAuthenticatedAction.cs
index 6f9248d..8c2bbf6 100644
--- a/source/Server.AzureAD/Web/AzureADUserAuthenticatedAction.cs
+++ b/source/Server.AzureAD/Web/AzureADUserAuthenticatedAction.cs
@@ -1,4 +1,5 @@
-using Octopus.Diagnostics;
+using Octopus.Core.Authentication;
+using Octopus.Diagnostics;
using Octopus.Server.Extensibility.Authentication.AzureAD.Configuration;
using Octopus.Server.Extensibility.Authentication.AzureAD.Identities;
using Octopus.Server.Extensibility.Authentication.AzureAD.Infrastructure;
@@ -22,7 +23,8 @@ public AzureADUserAuthenticatedAction(
ISleep sleep,
IAzureADIdentityCreator identityCreator,
IUrlEncoder encoder,
- IUserService userService) :
+ IUserService userService,
+ IOctopusAuthenticationConfigurationStore authenticationConfigurationStore) :
base(
log,
authTokenHandler,
@@ -33,7 +35,8 @@ public AzureADUserAuthenticatedAction(
sleep,
identityCreator,
encoder,
- userService)
+ userService,
+ authenticationConfigurationStore)
{
}
diff --git a/source/Server.AzureAD/Web/AzureADUserAuthenticatedPkceAction.cs b/source/Server.AzureAD/Web/AzureADUserAuthenticatedPkceAction.cs
index a8c5ab7..7360780 100644
--- a/source/Server.AzureAD/Web/AzureADUserAuthenticatedPkceAction.cs
+++ b/source/Server.AzureAD/Web/AzureADUserAuthenticatedPkceAction.cs
@@ -1,4 +1,5 @@
-using Octopus.Diagnostics;
+using Octopus.Core.Authentication;
+using Octopus.Diagnostics;
using Octopus.Server.Extensibility.Authentication.AzureAD.Configuration;
using Octopus.Server.Extensibility.Authentication.AzureAD.Identities;
using Octopus.Server.Extensibility.Authentication.AzureAD.Infrastructure;
@@ -26,7 +27,8 @@ public AzureADUserAuthenticatedPkceAction(
IUrlEncoder encoder,
IIdentityProviderConfigDiscoverer identityProviderConfigDiscoverer,
IMediator mediator,
- IUserService service)
+ IUserService service,
+ IOctopusAuthenticationConfigurationStore authenticationConfigurationStore)
: base(log,
authTokenHandler,
principalToUserResourceMapper,
@@ -38,7 +40,8 @@ public AzureADUserAuthenticatedPkceAction(
encoder,
identityProviderConfigDiscoverer,
mediator,
- service)
+ service,
+ authenticationConfigurationStore)
{
}
diff --git a/source/Server.GoogleApps/Server.GoogleApps.csproj b/source/Server.GoogleApps/Server.GoogleApps.csproj
index dd1d384..9330c73 100644
--- a/source/Server.GoogleApps/Server.GoogleApps.csproj
+++ b/source/Server.GoogleApps/Server.GoogleApps.csproj
@@ -36,5 +36,33 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Data.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Autofac.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.Base.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Diagnostics.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Time.dll
+
+
diff --git a/source/Server.OctopusID/Server.OctopusID.csproj b/source/Server.OctopusID/Server.OctopusID.csproj
index d32bc47..d4071f4 100644
--- a/source/Server.OctopusID/Server.OctopusID.csproj
+++ b/source/Server.OctopusID/Server.OctopusID.csproj
@@ -14,10 +14,6 @@
true
-
-
-
-
@@ -27,5 +23,33 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Data.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Autofac.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.Base.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Diagnostics.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Time.dll
+
+
\ No newline at end of file
diff --git a/source/Server.Okta/Server.Okta.csproj b/source/Server.Okta/Server.Okta.csproj
index faa294c..54495b6 100644
--- a/source/Server.Okta/Server.Okta.csproj
+++ b/source/Server.Okta/Server.Okta.csproj
@@ -14,10 +14,6 @@
true
-
-
-
-
@@ -28,5 +24,33 @@
-
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Data.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Autofac.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.Base.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Diagnostics.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Time.dll
+
+
\ No newline at end of file
diff --git a/source/Server.OpenIDConnect.Common/Server.OpenIDConnect.Common.csproj b/source/Server.OpenIDConnect.Common/Server.OpenIDConnect.Common.csproj
index 8d43ece..895f129 100644
--- a/source/Server.OpenIDConnect.Common/Server.OpenIDConnect.Common.csproj
+++ b/source/Server.OpenIDConnect.Common/Server.OpenIDConnect.Common.csproj
@@ -14,12 +14,7 @@
-
-
-
-
-
@@ -30,4 +25,33 @@
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Data.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Autofac.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.Base.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Diagnostics.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Time.dll
+
+
\ No newline at end of file
diff --git a/source/Server.OpenIDConnect.Common/Web/AuthServerResponseHandler.cs b/source/Server.OpenIDConnect.Common/Web/AuthServerResponseHandler.cs
index 6bb75a0..9a66098 100644
--- a/source/Server.OpenIDConnect.Common/Web/AuthServerResponseHandler.cs
+++ b/source/Server.OpenIDConnect.Common/Web/AuthServerResponseHandler.cs
@@ -88,7 +88,7 @@ public InvalidLoginAction CheckIfAuthenticationAttemptIsBanned(string username,
public IOctoResponseProvider Success(IOctoRequest request, ISuccessResult successResult, string username, LoginState state)
{
- loginTracker.RecordSucess(username, request.Host);
+ loginTracker.RecordSuccess(username, request.Host);
UserAuthenticatedValidator.ValidateUserIsActive(successResult.Value.IsActive, username);
UserAuthenticatedValidator.ValidateUserIsNotServiceAccount(successResult.Value.IsService, username);
diff --git a/source/Tests/Tests.csproj b/source/Tests/Tests.csproj
index 236165f..ac363e3 100644
--- a/source/Tests/Tests.csproj
+++ b/source/Tests/Tests.csproj
@@ -23,4 +23,33 @@
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Data.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Extensibility.Authentication.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Autofac.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.Client.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Server.MessageContracts.Base.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Diagnostics.dll
+
+
+ C:\Program Files\Octopus Deploy\Octopus\Octopus.Time.dll
+
+
\ No newline at end of file