Skip to content

Commit 014b577

Browse files
Update for release 3.3.0.11 (#284)
- Migrate from ProvisioningAPI to Graph API - Fixed some typos - Merged PR for Windows Server 2025 support: #269 - Implemented filtering of ASR Rule based on presence of exchange - Updated readme.md --------- Signed-off-by: HerbieSmith-Netwrix <Herbert.Smith@netwrix.com>
1 parent efd9dde commit 014b577

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1213
-530
lines changed

ADWS/ADWSConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ private void EnumerateInternalWithADWS(string distinguishedName, string filter,
421421
}
422422
}
423423
}
424-
throw new PingCastleException("An ADWS exception occured (fault:" + ex.Message + ";reason:" + ex.Reason + ").\r\nADWS is a faster protocol than LDAP but bound to a default 30 minutes limitation. If this error persists, we recommand to force the LDAP protocol. Run PingCastle with the following switches: --protocol LDAPOnly --interactive");
424+
throw new PingCastleException("An ADWS exception occured (fault:" + ex.Message + ";reason:" + ex.Reason + ").\r\nADWS is a faster protocol than LDAP but bound to a default 30 minutes limitation. If this error persists, we recommend to force the LDAP protocol. Run PingCastle with the following switches: --protocol LDAPOnly --interactive");
425425
}
426426
Trace.WriteLine("[" + DateTime.Now.ToLongTimeString() + "]Pull successful");
427427
if (pullResponse.EndOfSequence != null)

Bot/Bot.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using PingCastle.Data;
22
using PingCastle.Healthcheck;
3+
using PingCastle.PingCastleLicense;
34
using PingCastle.Report;
45
using PingCastle.Rules;
56
using System;
@@ -195,7 +196,7 @@ private BotInputOutput ToHtml(BotInputOutput input)
195196
{
196197
HealthcheckData healthcheckData = DataHelper<HealthcheckData>.LoadXml(ms, "bot", null);
197198
var endUserReportGenerator = new ReportHealthCheckSingle();
198-
var license = LicenseManager.Validate(typeof(Program), new Program()) as ADHealthCheckingLicense;
199+
var license = LicenseCache.Instance.GetLicense();
199200
var report = endUserReportGenerator.GenerateReportFile(healthcheckData, license, healthcheckData.GetHumanReadableFileName());
200201

201202
var o = new BotInputOutput();

Cloud/Analyzer/Analyzer.cs

Lines changed: 278 additions & 165 deletions
Large diffs are not rendered by default.

Cloud/Credentials/CertificateCredential.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace PingCastle.Cloud.Credentials
2424
{
2525
public class CertificateCredential : IDisposable, IAzureCredential
2626
{
27+
public bool ForceRefreshByRefreshToken { get; set; }
2728
private CertificateCredential()
2829
{
2930

Cloud/Credentials/CredentialBase.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,44 @@ public CredentialBase(string tenantid)
2727

2828
Dictionary<Type, Token> cache = new Dictionary<Type, Token>();
2929
public Token LastTokenQueried { get; protected set; }
30+
31+
public bool ForceRefreshByRefreshToken { get; set; }
32+
3033
public async Task<Token> GetToken<T>() where T : IAzureService
3134
{
32-
Token token;
3335
if (cache.ContainsKey(typeof(T)))
3436
{
35-
token = cache[typeof(T)];
37+
var caschedToken = cache[typeof(T)];
38+
39+
var networkLatency = 5;
40+
var expiresOn = DateTimeOffset.FromUnixTimeSeconds(caschedToken.expires_on).AddSeconds(-networkLatency);
41+
42+
if (expiresOn <= DateTime.UtcNow || ForceRefreshByRefreshToken)
43+
{
44+
caschedToken = await TokenFactory.RefreshToken<T>(tenantId, caschedToken);
45+
UpdateTokenCache<T>(caschedToken);
46+
}
47+
48+
return caschedToken;
49+
}
50+
51+
var newToken = await TokenFactory.GetToken<T>(this);
52+
UpdateTokenCache<T>(newToken);
3653

37-
// TODO refresh
54+
return newToken;
55+
}
3856

39-
return token;
57+
private void UpdateTokenCache<T>(Token token) where T : IAzureService
58+
{
59+
if (token.expires_on == 0)
60+
{
61+
token.expires_on = (uint)((DateTimeOffset)DateTime.UtcNow.AddSeconds(token.expires_in)).ToUnixTimeSeconds();
4062
}
41-
token = await TokenFactory.GetToken<T>(this);
63+
4264
LastTokenQueried = token;
4365
cache[typeof(T)] = token;
44-
return token;
4566
}
67+
4668
string tenantId;
4769
public string Tenantid
4870
{

Cloud/Credentials/IAzureCredential.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ public interface IAzureCredential
2020
string TenantidToQuery { get; set; }
2121
Task<Token> GetToken<T>() where T : IAzureService;
2222
Token LastTokenQueried { get; }
23+
bool ForceRefreshByRefreshToken { get; set; }
2324
}
2425
}

Cloud/Data/HealthCheckCloudData.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,6 @@ string ComputeIntegrity()
330330

331331
public string ProvisionDirectorySynchronizationStatus { get; set; }
332332

333-
public List<string> ProvisionCompanyTags { get; set; }
334-
335-
public string ProvisionCompanyType { get; set; }
336-
337333
public bool? ProvisionPasswordSynchronizationEnabled { get; set; }
338334

339335
public List<string> ProvisionAuthorizedServiceInstances { get; set; }
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Kiota.Abstractions.Authentication;
7+
using PingCastle.Cloud.Credentials;
8+
using PingCastle.Cloud.RESTServices.Azure;
9+
10+
namespace PingCastle.Cloud.MsGraph
11+
{
12+
public class AzureCredentialTokenProvider<T> : IAccessTokenProvider where T : IAzureService
13+
{
14+
private readonly IAzureCredential _credential;
15+
16+
public AzureCredentialTokenProvider(IAzureCredential credential)
17+
{
18+
_credential = credential;
19+
}
20+
21+
public async Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default, CancellationToken cancellationToken = default)
22+
{
23+
Trace.WriteLine($"ACTP: the token has been requested {uri}");
24+
var token = await _credential.GetToken<T>();
25+
return token.access_token;
26+
}
27+
28+
public AllowedHostsValidator AllowedHostsValidator => new AllowedHostsValidator();
29+
}
30+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using PingCastle.Cloud.Credentials;
2+
3+
namespace PingCastle.Cloud.MsGraph
4+
{
5+
public static class GraphApiClientFactory
6+
{
7+
public static IGraphApiClient Create(IAzureCredential credential)
8+
{
9+
var client = GraphServiceClientFactory.Create<MsGraphApiFacade>(credential);
10+
return new MsGraphApiFacade(client);
11+
}
12+
}
13+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.Graph;
2+
using Microsoft.Graph.Beta;
3+
using Microsoft.Kiota.Abstractions.Authentication;
4+
using PingCastle.Cloud.Credentials;
5+
using PingCastle.Cloud.RESTServices.Azure;
6+
7+
namespace PingCastle.Cloud.MsGraph
8+
{
9+
public static class GraphServiceClientFactory
10+
{
11+
public static GraphServiceClient Create(string accessToken)
12+
{
13+
return Create(new SimpleAccessTokenProvider(accessToken));
14+
}
15+
16+
public static GraphServiceClient Create<T>(IAzureCredential credential) where T : IAzureService
17+
{
18+
return Create(new AzureCredentialTokenProvider<T>(credential));
19+
}
20+
21+
public static GraphServiceClient Create(IAccessTokenProvider tokenProvider)
22+
{
23+
var authProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider);
24+
var httpClient = GraphClientFactory.Create(authProvider, version: "beta");
25+
26+
return new GraphServiceClient(httpClient);
27+
}
28+
}
29+
}

Cloud/MsGraph/IGraphApiClient.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using Microsoft.Graph.Beta.Models;
4+
5+
namespace PingCastle.Cloud.MsGraph
6+
{
7+
public interface IGraphApiClient
8+
{
9+
Task<Organization> GetCompanyInfoAsync();
10+
Task<AuthorizationPolicy> GetAuthorizationPolicyAsync();
11+
Task<OnPremisesDirectorySynchronization> GetOnPremisesDirectorySynchronizationAsync();
12+
Task<IReadOnlyList<LicenseDetails>> GetUserLicensesAsync(string userId);
13+
IAsyncEnumerable<DirectoryRole> GetRolesAsync();
14+
IAsyncEnumerable<DirectoryRoleTemplate> GetRoleTemplatesAsync();
15+
Task<IReadOnlyList<Domain>> GetDomainsAsync();
16+
Task<IReadOnlyList<DomainDnsRecord>> GetDomainDnsRecordsAsync(string domainName);
17+
IAsyncEnumerable<UserRegistrationDetails> GetUserRegistrationDetailsAsync();
18+
}
19+
}

Cloud/MsGraph/MsGraphApiFacade.cs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Threading.Tasks;
7+
using Microsoft.Graph.Beta;
8+
using Microsoft.Graph.Beta.Models;
9+
using PingCastle.Cloud.MsGraph;
10+
using PingCastle.Cloud.RESTServices.Azure;
11+
12+
namespace PingCastle.Cloud
13+
{
14+
[AzureService("1b730954-1685-4b74-9bfd-dac224a7b894", "https://graph.microsoft.com")]
15+
[EndPoint("https://login.microsoftonline.com/common/oauth2/v2.0/authorize", "https://login.microsoftonline.com/common/oauth2/v2.0/token", "https://graph.microsoft.com/.default")]
16+
public class MsGraphApiFacade : IAzureService, IGraphApiClient
17+
{
18+
private readonly GraphServiceClient _client;
19+
20+
public MsGraphApiFacade(GraphServiceClient client)
21+
{
22+
_client = client;
23+
}
24+
25+
public async Task<Organization> GetCompanyInfoAsync()
26+
{
27+
var companies = await _client.Organization.GetAsync();
28+
return companies.Value.FirstOrDefault();
29+
}
30+
31+
public async Task<AuthorizationPolicy> GetAuthorizationPolicyAsync()
32+
{
33+
var policies = await _client.Policies.AuthorizationPolicy.GetAsync();
34+
return policies.Value.FirstOrDefault();
35+
}
36+
37+
public async Task<OnPremisesDirectorySynchronization> GetOnPremisesDirectorySynchronizationAsync()
38+
{
39+
var onPremSync = await _client.Directory.OnPremisesSynchronization.GetAsync();
40+
return onPremSync.Value.FirstOrDefault();
41+
}
42+
43+
public async Task<IReadOnlyList<LicenseDetails>> GetUserLicensesAsync(string userId)
44+
{
45+
try
46+
{
47+
var licenses = await _client.Users[userId].LicenseDetails.GetAsync();
48+
return licenses.Value;
49+
}
50+
catch (Exception ex)
51+
{
52+
Trace.TraceError($"User id: {userId} not found. Error: {ex}");
53+
return null;
54+
}
55+
}
56+
57+
public async IAsyncEnumerable<DirectoryRole> GetRolesAsync()
58+
{
59+
var roles = await _client.DirectoryRoles
60+
.GetAsync(configuration =>
61+
{
62+
configuration.QueryParameters.Expand = new[] { "members" };
63+
});
64+
65+
do
66+
{
67+
foreach (var role in roles.Value)
68+
{
69+
yield return role;
70+
}
71+
}
72+
while (roles.OdataNextLink != null &&
73+
(roles = await _client.DirectoryRoles.WithUrl(roles.OdataNextLink).GetAsync()) != null);
74+
}
75+
76+
public async IAsyncEnumerable<DirectoryRoleTemplate> GetRoleTemplatesAsync()
77+
{
78+
var templates = await _client.DirectoryRoleTemplates.GetAsync();
79+
80+
do
81+
{
82+
foreach (var template in templates.Value)
83+
{
84+
yield return template;
85+
}
86+
}
87+
while (templates.OdataNextLink != null &&
88+
(templates = await _client.DirectoryRoleTemplates.WithUrl(templates.OdataNextLink).GetAsync()) != null);
89+
}
90+
91+
public async Task<IReadOnlyList<Domain>> GetDomainsAsync()
92+
{
93+
var domains = await _client.Domains.GetAsync(configuration =>
94+
{
95+
configuration.QueryParameters.Expand = new[] { "rootDomain" };
96+
});
97+
return domains.Value;
98+
}
99+
100+
public async Task<IReadOnlyList<DomainDnsRecord>> GetDomainDnsRecordsAsync(string domainName)
101+
{
102+
try
103+
{
104+
var records = await _client.Domains[domainName].VerificationDnsRecords.GetAsync();
105+
return records.Value;
106+
}
107+
catch (Exception ex)
108+
{
109+
Trace.TraceError($"Domain id: {domainName} not found. Error: {ex}");
110+
return null;
111+
}
112+
}
113+
114+
public async IAsyncEnumerable<UserRegistrationDetails> GetUserRegistrationDetailsAsync()
115+
{
116+
var details = await _client.Reports.AuthenticationMethods.UserRegistrationDetails
117+
.GetAsync(configuration =>
118+
{
119+
configuration.QueryParameters.Select = new[] { "id", "isMfaRegistered", "isMfaCapable" };
120+
});
121+
122+
do
123+
{
124+
foreach (var detail in details.Value)
125+
{
126+
yield return detail;
127+
}
128+
}
129+
while (details.OdataNextLink != null &&
130+
(details = await _client.Reports.AuthenticationMethods.UserRegistrationDetails.WithUrl(details.OdataNextLink).GetAsync()) != null);
131+
}
132+
}
133+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Kiota.Abstractions.Authentication;
5+
using System;
6+
7+
namespace PingCastle.Cloud.MsGraph
8+
{
9+
public class SimpleAccessTokenProvider : IAccessTokenProvider
10+
{
11+
private readonly string _accessToken;
12+
13+
public SimpleAccessTokenProvider(string accessToken)
14+
{
15+
_accessToken = accessToken;
16+
}
17+
18+
public async Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default, CancellationToken cancellationToken = default)
19+
{
20+
return await Task.FromResult(_accessToken);
21+
}
22+
23+
public AllowedHostsValidator AllowedHostsValidator => new AllowedHostsValidator();
24+
}
25+
}

Cloud/RESTServices/Azure/ProvisioningApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,7 +1678,7 @@ public object BeforeSendRequest(ref Message request, IClientChannel channel)
16781678
request.Headers.Add(myHeader);
16791679
ClientVersionHeader clientVersionHeader = new ClientVersionHeader();
16801680
clientVersionHeader.ClientId = new Guid("50afce61-c917-435b-8c6d-60aa5a8b8aa7");
1681-
clientVersionHeader.Version = "1.2.183.66";
1681+
clientVersionHeader.Version = "1.2.183.81";
16821682
request.Headers.Add(MessageHeader.CreateHeader("ClientVersionHeader", "http://provisioning.microsoftonline.com/", clientVersionHeader));
16831683
ContractVersionHeader contractVersionHeader = new ContractVersionHeader();
16841684
contractVersionHeader.BecVersion = Version.Version47;

Cloud/RESTServices/EndPointAttribute.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,22 @@
55
// Licensed under the Non-Profit OSL. See LICENSE file in the project root for full license information.
66
//
77
using System;
8-
using System.Collections.Generic;
9-
using System.Linq;
10-
using System.Text;
11-
using System.Threading.Tasks;
128
using System.Reflection;
139

1410
namespace PingCastle.Cloud.RESTServices.Azure
1511
{
1612
public class EndPointAttribute : Attribute
1713
{
18-
public EndPointAttribute(string Authorize, string Token)
14+
public EndPointAttribute(string authorize, string token, string scope = null)
1915
{
20-
this.AuthorizeEndPoint = Authorize;
21-
this.TokenEndPoint = Token;
16+
AuthorizeEndPoint = authorize;
17+
TokenEndPoint = token;
18+
Scope = scope;
2219
}
2320

2421
public string AuthorizeEndPoint { get; private set; }
2522
public string TokenEndPoint { get; private set; }
23+
public string Scope { get; private set; }
2624

2725
public static EndPointAttribute GetEndPointAttribute<T>() where T : IAzureService
2826
{

0 commit comments

Comments
 (0)