Skip to content

Commit 5820269

Browse files
authored
Multiple issues addressed. (#25)
1. When the group membership to be removed is inlined in the path, it is now removed correctly from the group. 2. Members already part of the group cannot be re-added to the group. 3. Patching of the extension attribute used to fail if the user was not created using the extension attribute. That has been fixed. 4. Sundry formatting changes and fixes.
1 parent 49d65f5 commit 5820269

File tree

10 files changed

+143
-99
lines changed

10 files changed

+143
-99
lines changed

Microsoft.SCIM.WebHostSample/Controllers/TokenController.cs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,51 @@
1-
// Copyright (c) Microsoft Corporation.// Licensed under the MIT license.
2-
3-
using Microsoft.AspNetCore.Mvc;
4-
using Microsoft.Extensions.Configuration;
5-
using Microsoft.IdentityModel.Tokens;
6-
using System;
7-
using System.Collections.Generic;
8-
using System.IdentityModel.Tokens.Jwt;
9-
using System.Linq;
10-
using System.Text;
11-
using System.Threading.Tasks;
1+
//------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
//------------------------------------------------------------
124

135
namespace Microsoft.SCIM.WebHostSample.Controllers
146
{
7+
using System;
8+
using System.IdentityModel.Tokens.Jwt;
9+
using System.Text;
10+
using Microsoft.AspNetCore.Mvc;
11+
using Microsoft.Extensions.Configuration;
12+
using Microsoft.IdentityModel.Tokens;
13+
1514
// Controller for generating a bearer token for authorization during testing.
1615
// This is not meant to replace proper Oauth for authentication purposes.
1716
[Route("scim/token")]
1817
[ApiController]
1918
public class TokenController : ControllerBase
2019
{
21-
private readonly IConfiguration _configuration;
22-
23-
private const int defaultTokenExpiration = 120;
20+
private readonly IConfiguration configuration;
21+
private const int defaultTokenExpirationTimeInMins = 120;
2422

2523
public TokenController(IConfiguration Configuration)
2624
{
27-
_configuration = Configuration;
25+
this.configuration = Configuration;
2826
}
2927

3028
private string GenerateJSONWebToken()
3129
{
3230
// Create token key
3331
SymmetricSecurityKey securityKey =
34-
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._configuration["Token:IssuerSigningKey"]));
32+
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.configuration["Token:IssuerSigningKey"]));
3533
SigningCredentials credentials =
3634
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
3735

3836
// Set token expiration
3937
DateTime startTime = DateTime.UtcNow;
4038
DateTime expiryTime;
41-
double tokenExpiration;
42-
if (double.TryParse(this._configuration["Token:TokenLifetimeInMins"], out tokenExpiration))
39+
if (double.TryParse(this.configuration["Token:TokenLifetimeInMins"], out double tokenExpiration))
4340
expiryTime = startTime.AddMinutes(tokenExpiration);
4441
else
45-
expiryTime = startTime.AddMinutes(defaultTokenExpiration);
42+
expiryTime = startTime.AddMinutes(defaultTokenExpirationTimeInMins);
4643

4744
// Generate the token
4845
JwtSecurityToken token =
4946
new JwtSecurityToken(
50-
this._configuration["Token:TokenIssuer"],
51-
this._configuration["Token:TokenAudience"],
47+
this.configuration["Token:TokenIssuer"],
48+
this.configuration["Token:TokenAudience"],
5249
null,
5350
notBefore: startTime,
5451
expires: expiryTime,

Microsoft.SCIM.WebHostSample/Program.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
// Copyright (c) Microsoft Corporation.// Licensed under the MIT license.
2-
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Threading.Tasks;
7-
using Microsoft.AspNetCore.Hosting;
8-
using Microsoft.Extensions.Configuration;
9-
using Microsoft.Extensions.Hosting;
10-
using Microsoft.Extensions.Logging;
1+
//------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
//------------------------------------------------------------
114

125
namespace Microsoft.SCIM.WebHostSample
136
{
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.Extensions.Hosting;
9+
1410
public class Program
1511
{
1612
public static void Main(string[] args)
1713
{
18-
CreateHostBuilder(args).Build().Run();
14+
Program.CreateHostBuilder(args).Build().Run();
1915
}
2016

2117
public static IHostBuilder CreateHostBuilder(string[] args) =>

Microsoft.SCIM.WebHostSample/Startup.cs

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
1-
// Copyright (c) Microsoft Corporation.// Licensed under the MIT license.
2-
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
8-
using Microsoft.AspNetCore.Authentication.JwtBearer;
9-
using Microsoft.AspNetCore.Builder;
10-
using Microsoft.AspNetCore.Hosting;
11-
using Microsoft.AspNetCore.Http;
12-
using Microsoft.AspNetCore.Routing;
13-
using Microsoft.Extensions.Configuration;
14-
using Microsoft.Extensions.DependencyInjection;
15-
using Microsoft.Extensions.Hosting;
16-
using Microsoft.IdentityModel.Tokens;
17-
using Microsoft.SCIM.WebHostSample.Provider;
1+
//------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
//------------------------------------------------------------
184

195
namespace Microsoft.SCIM.WebHostSample
206
{
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Authentication.JwtBearer;
10+
using Microsoft.AspNetCore.Builder;
11+
using Microsoft.AspNetCore.Hosting;
12+
using Microsoft.AspNetCore.Routing;
13+
using Microsoft.Extensions.Configuration;
14+
using Microsoft.Extensions.DependencyInjection;
15+
using Microsoft.Extensions.Hosting;
16+
using Microsoft.IdentityModel.Tokens;
17+
using Microsoft.SCIM.WebHostSample.Provider;
18+
2119
public class Startup
2220
{
23-
private readonly IWebHostEnvironment _env;
24-
private readonly IConfiguration _configuration;
21+
private readonly IWebHostEnvironment environment;
22+
private readonly IConfiguration configuration;
2523

2624
public IMonitor MonitoringBehavior { get; set; }
2725
public IProvider ProviderBehavior { get; set; }
2826

2927
public Startup(IWebHostEnvironment env, IConfiguration configuration)
3028
{
31-
this._env = env;
32-
this._configuration = configuration;
29+
this.environment = env;
30+
this.configuration = configuration;
3331

3432
this.MonitoringBehavior = new ConsoleMonitor();
3533
this.ProviderBehavior = new InMemoryProvider();
@@ -39,7 +37,7 @@ public Startup(IWebHostEnvironment env, IConfiguration configuration)
3937
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
4038
public void ConfigureServices(IServiceCollection services)
4139
{
42-
if (_env.IsDevelopment())
40+
if (this.environment.IsDevelopment())
4341
{
4442
// Development environment code
4543
// Validation for bearer token for authorization used during testing.
@@ -51,20 +49,20 @@ public void ConfigureServices(IServiceCollection services)
5149
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
5250
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
5351
})
54-
.AddJwtBearer(options =>
55-
{
56-
options.TokenValidationParameters =
57-
new TokenValidationParameters
58-
{
59-
ValidateIssuer = false,
60-
ValidateAudience = false,
61-
ValidateLifetime = false,
62-
ValidateIssuerSigningKey = false,
63-
ValidIssuer = this._configuration["Token:TokenIssuer"],
64-
ValidAudience = this._configuration["Token:TokenAudience"],
65-
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._configuration["Token:IssuerSigningKey"]))
66-
};
67-
});
52+
.AddJwtBearer(options =>
53+
{
54+
options.TokenValidationParameters =
55+
new TokenValidationParameters
56+
{
57+
ValidateIssuer = false,
58+
ValidateAudience = false,
59+
ValidateLifetime = false,
60+
ValidateIssuerSigningKey = false,
61+
ValidIssuer = this.configuration["Token:TokenIssuer"],
62+
ValidAudience = this.configuration["Token:TokenAudience"],
63+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.configuration["Token:IssuerSigningKey"]))
64+
};
65+
});
6866
}
6967
else
7068
{
@@ -79,33 +77,32 @@ public void ConfigureServices(IServiceCollection services)
7977
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
8078
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
8179
})
82-
.AddJwtBearer(options =>
80+
.AddJwtBearer(options =>
81+
{
82+
options.Authority = this.configuration["Token:TokenIssuer"];
83+
options.Audience = this.configuration["Token:TokenAudience"];
84+
options.Events = new JwtBearerEvents
8385
{
84-
options.Authority = this._configuration["Token:TokenIssuer"];
85-
options.Audience = this._configuration["Token:TokenAudience"];
86-
options.Events = new JwtBearerEvents
86+
OnTokenValidated = context =>
8787
{
88-
OnTokenValidated = context =>
89-
{
90-
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.
88+
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.
9189

92-
return Task.CompletedTask;
93-
},
94-
OnAuthenticationFailed = AuthenticationFailed
95-
};
96-
});
90+
return Task.CompletedTask;
91+
},
92+
OnAuthenticationFailed = AuthenticationFailed
93+
};
94+
});
9795
}
9896

9997
services.AddControllers().AddNewtonsoftJson();
100-
10198
services.AddSingleton(typeof(IProvider), this.ProviderBehavior);
10299
services.AddSingleton(typeof(IMonitor), this.MonitoringBehavior);
103100
}
104101

105102
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
106103
public void Configure(IApplicationBuilder app)
107104
{
108-
if (_env.IsDevelopment())
105+
if (this.environment.IsDevelopment())
109106
{
110107
app.UseDeveloperExceptionPage();
111108
}
@@ -127,10 +124,13 @@ public void Configure(IApplicationBuilder app)
127124
private Task AuthenticationFailed(AuthenticationFailedContext arg)
128125
{
129126
// For debugging purposes only!
130-
var s = $"{{AuthenticationFailed: '{arg.Exception.Message}'}}";
127+
string authenticationExceptionMessage = $"{{AuthenticationFailed: '{arg.Exception.Message}'}}";
131128

132-
arg.Response.ContentLength = s.Length;
133-
arg.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(s), 0, s.Length);
129+
arg.Response.ContentLength = authenticationExceptionMessage.Length;
130+
arg.Response.Body.WriteAsync(
131+
Encoding.UTF8.GetBytes(authenticationExceptionMessage),
132+
0,
133+
authenticationExceptionMessage.Length);
134134

135135
return Task.FromException(arg.Exception);
136136
}

Microsoft.SystemForCrossDomainIdentityManagement/Microsoft.SCIM.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,17 @@
5151
<EmbeddedResource Update="SystemForCrossDomainIdentityManagementServiceResources.resx">
5252
<Generator>ResXFileCodeGenerator</Generator>
5353
<LastGenOutput>SystemForCrossDomainIdentityManagementServiceResources.Designer.cs</LastGenOutput>
54+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
5455
</EmbeddedResource>
5556
<EmbeddedResource Update="SystemForCrossDomainIdentityManagementProtocolResources.resx">
5657
<Generator>ResXFileCodeGenerator</Generator>
5758
<LastGenOutput>SystemForCrossDomainIdentityManagementProtocolResources.Designer.cs</LastGenOutput>
59+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
5860
</EmbeddedResource>
5961
<EmbeddedResource Update="SystemForCrossDomainIdentityManagementSchemasResources.resx">
6062
<Generator>ResXFileCodeGenerator</Generator>
6163
<LastGenOutput>SystemForCrossDomainIdentityManagementSchemasResources.Designer.cs</LastGenOutput>
64+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
6265
</EmbeddedResource>
6366
</ItemGroup>
6467

Microsoft.SystemForCrossDomainIdentityManagement/Protocol/Filter.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,10 @@ public static string ToString(IReadOnlyCollection<IFilter> filters)
299299
Filter clone = new Filter(filter);
300300
clone.ComparisonValue = placeholder;
301301
string currentFilter = clone.Serialize();
302-
string encodedFilter = HttpUtility.UrlEncode(currentFilter).Replace(placeholder, filter.ComparisonValueEncoded, StringComparison.InvariantCulture);
302+
string encodedFilter =
303+
HttpUtility
304+
.UrlEncode(currentFilter)
305+
.Replace(placeholder, filter.ComparisonValueEncoded, StringComparison.InvariantCulture);
303306
if (string.IsNullOrWhiteSpace(allFilters))
304307
{
305308
allFilters =
@@ -333,11 +336,13 @@ public static string ToString(IReadOnlyCollection<IFilter> filters)
333336

334337
public static bool TryParse(string filterExpression, out IReadOnlyCollection<IFilter> filters)
335338
{
336-
if(filterExpression == null)
339+
string expression = filterExpression?.Trim()?.Unquote();
340+
341+
if (string.IsNullOrWhiteSpace(expression))
337342
{
338343
throw new ArgumentNullException(nameof(filterExpression));
339344
}
340-
string expression = filterExpression.Trim().Unquote();
345+
341346
try
342347
{
343348
IReadOnlyCollection<IFilter> buffer = new FilterExpression(expression).ToFilters();

Microsoft.SystemForCrossDomainIdentityManagement/Protocol/PatchOperation2Combined.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Microsoft.SCIM
77
using System;
88
using System.Collections.Generic;
99
using System.Globalization;
10+
using System.Linq;
1011
using System.Runtime.Serialization;
1112
using Newtonsoft.Json;
1213

@@ -32,7 +33,13 @@ public string Value
3233
{
3334
get
3435
{
35-
return JsonConvert.SerializeObject(this.values);
36+
if (this.values == null)
37+
{
38+
return null;
39+
}
40+
41+
string result = JsonConvert.SerializeObject(this.values);
42+
return result;
3643
}
3744

3845
set
@@ -41,6 +48,26 @@ public string Value
4148
}
4249
}
4350

51+
[OnDeserialized]
52+
private void OnDeserialized(StreamingContext context)
53+
{
54+
if (this.Value == null)
55+
{
56+
if
57+
(
58+
this?.Path?.AttributePath != null &&
59+
this.Path.AttributePath.Contains(AttributeNames.Members, StringComparison.OrdinalIgnoreCase) &&
60+
this.Name == SCIM.OperationName.Remove &&
61+
this.Path?.SubAttributes?.Count == 1
62+
)
63+
{
64+
this.Value = this.Path.SubAttributes.First().ComparisonValue;
65+
IPath path = SCIM.Path.Create(AttributeNames.Members);
66+
this.Path = path;
67+
}
68+
}
69+
}
70+
4471
public override string ToString()
4572
{
4673
string allValues = string.Join(Environment.NewLine, this.Value);

0 commit comments

Comments
 (0)